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

The analyzer has detected an arithmetic expression where a signed integer overflow may occur.

The example:

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

According to the C and C++ rules, the result type of the `longOperand * 0xFFFF` expression will be `long`. On Windows, when using the MSVC compiler, the `long` type is 4 bytes in size. The maximum value that can be represented by this type is 2'147'483'647 in decimal or 0x7FFF'FFFF in hexadecimal. When multiplying the `longOperand` variable by 0xFFFF (65,535), the 0x7FFF'7FFF'0001 result is expected. However, as specified in the C18 standard (section 6.5, paragraph 5) and C++20 (section 7.1, paragraph 4), the signed integer overflow leads to undefined behavior.

There are several ways to fix this code.

To evaluate expressions correctly, use types with a size large enough to hold the expected value. If the value does not fit a machine word, use one of the libraries for arbitrary-precision arithmetic, such as GMP, MPRF, and cnl.

The code fragment above can be fixed as follows:

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

To handle the signed integer overflow as an unintended behavior, use specialized libraries that provide safe integer operations, such as boost::safe_numerics or Google Integers.

To implement wraparound arithmetic for signed integers with standard-defined behavior, use unsigned integers for evaluations. When the unsigned integer overflow occurs, the integer wraps around modulo `2 ^ n`, where `n` is the number of bits within the integer.

The possible solution based on `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));
      }
    }

The `std::bit_cast` function converts `lhs` and `rhs` to the corresponding unsigned representations. Next, some arithmetic operation is performed on the two converted operands. After that, the result is expanded or narrowed to the required result type and converted to a signed one.

With this approach, signed integers follow the semantics of unsigned ones in arithmetic operations. This does not lead to undefined behavior.

For example, you can see here that the compiler may optimize the code if it detects the signed integer overflow.

The example:

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

If `a` equals `MAX_INT`, the `a + 1 < a` condition will be `false`. This is a way to check whether an overflow has occurred. However, the compiler generates the following code:

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

The assembly `xor eax, eax` instruction resets the result of the `is_max_int` function execution. As a result, the latter function always returns `true`, no matter what the `a` value has. In this case, this is the result of undefined behavior due to overflow.

In the case of an unsigned representation, undefined behavior does not occur:

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

The compiler has generated code that does check the condition.

Note. The diagnostic rule supports a special setting that enables the analyzer to detect overflow in both signed and unsigned numeric types.

To enable this setting, add the following comment to the source code or to the diagnostic rules configuration file (`.pvsconfig`):

`//+V1083 CHECK_UNSIGNED_OVERFLOW:YES`

To disable the setting, add the comment:

`//+V1083 CHECK_UNSIGNED_OVERFLOW:NO`

This diagnostic is classified as:

CWE-190 CERT-INT32-C

You can look at examples of errors detected by the V1083 diagnostic.