Анализатор обнаружил потенциальную ошибку, связанную со сдвигом целого числа на 'N' бит, при этом 'N' больше размера этого числового типа в битах.
Давайте рассмотрим пример:
UInt32 x = ....;
UInt32 y = ....;
UInt64 result = (x << 32) + y;
В данном случае хотели собрать 64-битное число из 2-х 32-битных, сдвинув 'x' на 32 бита и сложив старшую и младшую часть. Но поскольку 'x' в момент сдвига является 32-битным числом, то сдвиг на 32 бита эквивалентен сдвигу на 0 бит, что приведет к некорректному результату.
Корректный вариант может выглядеть так:
UInt32 x = ....;
UInt32 y = ....;
UInt64 result = ((UInt64)x << 32) + y;
Также давайте рассмотрим пример из реального проекта:
static long GetLong(byte[] bits)
{
return ((bits[0] & 0xff) << 0)
| ((bits[1] & 0xff) << 8)
| ((bits[2] & 0xff) << 16)
| ((bits[3] & 0xff) << 24)
| ((bits[4] & 0xff) << 32)
| ((bits[5] & 0xff) << 40)
| ((bits[6] & 0xff) << 48)
| ((bits[7] & 0xff) << 56);
}
В методе 'GetLong' выполняется преобразование массива байт в 64-битное число. Поскольку операторы битового сдвига определены только для 32 и 64-битных чисел, то каждый байт будет неявно преобразован в 'Int32'. Диапазон битового сдвига 32-битного числа - [0..31], поэтому преобразование будет выполняться корректно только для первых 4 байт массива.
Если предположить, что массив байт был сформирован из 64-битного числа (К примеру из 'Int64.MaxValue'), то при обратном преобразовании из массива байт в Int64 данным методом возможна ошибка, в случае, если изначальное число не находилось в диапазоне [Int32.MinValue....Int32.MaxValue].
Для большей наглядности давайте рассмотрим работу данного кода для числа '289077008695033855'. Данное число после преобразования в массив байт будет иметь вид:
289077008695033855 => [255, 255, 255, 255, 1, 2, 3, 4]
Передав этот массив байт в метод 'GetLong' перед операцией сдвига каждый байт будет неявно преобразован в Int32. Давайте выполним каждую операцию сдвига отдельно, чтобы понять в чем проблема.
Как видим, сдвиг выполняется для 32-битного числа, что в итоге приводит к пересечению диапазонов и как следствие к неправильному результату. Это происходит потому, что при попытке сдвинуть 32-битное число более чем на 32 бита мы начинаем сдвигать биты по кругу (сдвиг на 32, 40, 48 и 56 бит идентичен сдвигу на 0, 8, 16 и 24 соответственно)
Исправленный вариант данного метода мог бы выглядеть так:
static long GetLong(byte[] bits)
{
return ((long)(bits[0] & 0xff) << 0)
| ((long)(bits[1] & 0xff) << 8)
| ((long)(bits[2] & 0xff) << 16)
| ((long)(bits[3] & 0xff) << 24)
| ((long)(bits[4] & 0xff) << 32)
| ((long)(bits[5] & 0xff) << 40)
| ((long)(bits[6] & 0xff) << 48)
| ((long)(bits[7] & 0xff) << 56);
}
Рассмотрев каждую операцию сдвига отдельно, мы увидим, что теперь сдвиг выполняется для 64-битного числа, что в итоге препятствует пересечению диапазонов.
Выявляемые диагностикой ошибки классифицируются согласно ГОСТ Р 71207–2024 как критические и относятся к типу: Ошибки целочисленного переполнения и некорректного совместного использования знаковых и беззнаковых чисел. |
Данная диагностика классифицируется как:
Взгляните на примеры ошибок, обнаруженных с помощью диагностики V3134. |