# V1083. Signed integer overflow in arithmetic expression. This leads to undefined behavior.

Анализатор обнаружил арифметическое выражение, в котором может произойти переполнение знакового числа.

Рассмотрим пример:

    long long foo()
    {
      long longOperand = 0x7FFF'FFFF;
      long long y = longOperand * 0xFFFF;
      return y;
    }

По правилам C и C++ результирующим типом выражения `longOperand * 0xFFFF` будет `long`. При использовании компилятора MSVC на Windows тип `long` имеет размер 4 байта. Максимальное значение, которое может быть представлено этим типом, равно 2'147'483'647 в десятичной системе или 0x7FFF'FFFF в шестнадцатеричной. При умножении переменной `longOperand` на 0xFFFF (65 535) ожидается результат 0x7FFF'7FFF'0001. Однако согласно стандарту C (см. стандарт С18 пункт 6.5 параграф 5) и C++ (см. стандарт С++20 пункт 7.1 параграф 4) переполнение знаковых чисел приводит к неопределённому поведению.

Исправить этот код можно несколькими способами.

Если требуется произвести корректные вычисления, необходимо использовать типы, размеры которых будут достаточны для отображения чисел. Если число не помещается в машинное слово, то можно воспользоваться одной из библиотек для работы с длинной арифметикой. Например, GMP, MPRF, cnl.

Пример выше можно исправить следующим образом:

    long long foo()
    {
      long longOperand = 0x7FFF'FFFF;
      long long y = static_cast<long long>(longOperand) * 0xFFFF;
      return y;
    }

Если переполнение знаковых чисел является неожидаемым поведением и требует обработки, то можно воспользоваться специальными библиотеками для безопасной работы с числами. Например, boost::safe_numerics или Google Integers.

Если требуется реализовать циклическую арифметику для знаковых чисел с определённым по стандарту поведением, то для расчётов можно воспользоваться беззнаковыми числами. В случае их переполнения происходит "оборачивание" числа по модулю `2 ^ n`, где `n` — количество бит в числе.

Рассмотрим одно из возможных решений на основе `std::bit_cast` (C++20):

    #include <concepts>
    #include <type_traits>
    #include <bit>
    #include <functional>

    namespace detail
    {
      template <std::signed_integral R,
                std::signed_integral T1,
                std::signed_integral T2,
                std::invocable<std::make_unsigned_t<T1>,
                               std::make_unsigned_t<T2>> Fn>
      R safe_signed_wrapper(T1 lhs, T2 rhs, Fn &&op)
        noexcept(std::is_nothrow_invocable_v<Fn,
                                             std::make_unsigned_t<T1>,
                                             std::make_unsigned_t<T2>>)
      {
        auto uLhs = std::bit_cast<std::make_unsigned_t<T1>>(lhs);
        auto uRhs = std::bit_cast<std::make_unsigned_t<T2>>(rhs);

        auto res = std::invoke(std::forward<Fn>(op), uLhs, uRhs);

        using UR = std::make_unsigned_t<R>;
        return std::bit_cast<R>(static_cast<UR>(res));
      }
    }

Функция `std::bit_cast` приводит `lhs' и 'rhs` к соответствующим беззнаковым представлениям. Далее на двух преобразованных операндах выполняется некоторая арифметическая операция. Затем результат расширяется или сужается до нужного результирующего типа и превращается в знаковый.

При таком подходе знаковые числа будут повторять семантику беззнаковых в арифметических операциях, что исключает неопределённое поведение.

Например, по этой ссылке можно увидеть, что компилятор вправе оптимизировать код, если существует вероятность переполнения знакового числа.

Рассмотрим пример подробнее:

    bool is_max_int(int32_t a)
    {
      return a + 1 < a;
    }

Если `a` равно `MAX_INT`, то условие `a + 1 < a` будет равно `false`. Таким образом часто проверяют не произошло ли переполнение.

Однако компилятор генерирует такой код:

    is_max_int(int):                        # @is_max_int(int)
            xor     eax, eax
            ret

Инструкция ассемблера `xor eax, eax` обнуляет результат выполнения функции `is_max_int`. В результате последняя всегда возвращает `true` вне зависимости от значения `a`. В данном случае это результат неопределённого поведения при переполнении.

В случае применения беззнакового представления такого не происходит:

    is_max_int(int):                        # @is_max_int(int)
            cmp     edi, 2147483647
            sete    al
            ret

Компилятор сгенерировал код, который проверяет условие.

Примечание. Диагностическое правило поддерживает специальную настройку, позволяющую анализатору обнаруживать переполнение как знаковых, так и беззнаковых числовых типов.

Для активации этой настройки необходимо добавить следующий комментарий в исходный код или в файл конфигурации диагностических правил `.pvsconfig`:

`//+V1083 CHECK_UNSIGNED_OVERFLOW:YES`

Чтобы отключить настройку, используйте комментарий:

`//+V1083 CHECK_UNSIGNED_OVERFLOW:NO`

Выявляемые диагностикой ошибки классифицируются согласно ГОСТ Р 71207–2024 как критические и относятся к типу: Ошибки целочисленного переполнения и некорректного совместного использования знаковых и беззнаковых чисел, Ошибки непроверенного использования чувствительных данных (ввода пользователя, файлов, сети и пр.).

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

CWE-190 CERT-INT32-C

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