>
>
>
V3134. Shift by N bits is greater than …


V3134. Shift by N bits is greater than the size of type.

Анализатор обнаружил потенциальную ошибку, связанную со сдвигом целого числа на '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-битного числа, что в итоге препятствует пересечению диапазонов.

Данная диагностика классифицируется как:

Взгляните на примеры ошибок, обнаруженных с помощью диагностики V3134.