Consider this situation:
uint64_t add(uint32_t a, uint32_t b)
{
return a + b; // programmer neglected (uint64_t) a + b.
}
How do we get
I am not aware of a flag to GCC that will cause a warning. The Coverity static analyzer will issue an OVERFLOW_BEFORE_WIDEN warning as this is flagged in the CERT standards.
Disclaimer: I once worked for Coverity.
It has various checks for integer overflow including unsigned operations
C26450 RESULT_OF_ARITHMETIC_OPERATION_PROVABLY_LOSSY: [operator] operation causes overflow at compile time. Use a wider type to store the operands. This warning indicates that an arithmetic operation was provably lossy at compile time. This can be asserted when the operands are all compile-time constants. Currently, we check left shift, multiplication, addition, and subtraction operations for such overflows.
uint32_t multiply() { const uint32_t a = UINT_MAX; // the author used int here const uint32_t b = 2; // but I changed to unsigned for this question uint32_t c = a * b; // C26450 reported here [and also C4307] return c; }
C26451 RESULT_OF_ARITHMETIC_OPERATION_CAST_TO_LARGER_SIZE: Using operator [operator] on a [size1] byte value and then casting the result to a [size2] byte value. Cast the value to the wider type before calling operator [operator] to avoid overflow.
This warning indicates incorrect behavior that results from integral promotion rules and types larger than those in which arithmetic is typically performed. We detect when a narrow type integral value was shifted left, multiplied, added, or subtracted and the result of that arithmetic operation was cast to a wider type value. If the operation overflowed the narrow type value, then data is lost. You can prevent this loss by casting the value to a wider type before the arithmetic operation.
void leftshift(int i) { unsigned long long x; x = i << 31; // C26451 reported here // code // Corrected source: void leftshift(int i) { unsigned long long x; x = (unsigned long long)i << 31; // OK // code }
C26454 RESULT_OF_ARITHMETIC_OPERATION_NEGATIVE_UNSIGNED: [operator] operation wraps past 0 and produces a large unsigned number at compile time
This warning indicates that the subtraction operation produces a negative result which was evaluated in an unsigned context. This causes the result to wrap past 0 and produce a really large unsigned number, which can result in unintended overflows.
// Example source: unsigned int negativeunsigned() { const unsigned int x = 1u - 2u; // C26454 reported here return x; } // Corrected source: unsigned int negativeunsigned() { const unsigned int x = 4294967295; // OK return x; }
Arithmetic overflow checks in C++ Core Check
Here's an example of it in action
As you can see from the examples above, the compiler itself can also emit a warning if the operands were compile time constants. If they were variables then you need the static analyzer
You can play around with that on Compiler Explorer, although I'm not sure how to make it really work from command line. If you know how to pass arguments to VS code analysis please comment below. On MSVC GUI just press Alt+F11
For information on how to run the analysis read C++ Static Analysis Improvements for Visual Studio 2017 15.6 Preview 2
Clang doesn't have a compile-time option for that, but it has an option to check at runtime
-fsanitize=unsigned-integer-overflow
: Unsigned integer overflow, where the result of an unsigned integer computation cannot be represented in its type. Unlike signed integer overflow, this is not undefined behavior, but it is often unintentional. This sanitizer does not check for lossy implicit conversions performed before such a computation (see-fsanitize=implicit-conversion
).UndefinedBehaviorSanitizer
It can also be disabled easily
Silencing Unsigned Integer Overflow
To silence reports from unsigned integer overflow, you can set
UBSAN_OPTIONS=silence_unsigned_overflow=1
. This feature, combined with-fsanitize-recover=unsigned-integer-overflow
, is particularly useful for providing fuzzing signal without blowing up logs.
Unfortunately GCC only supports -fsanitize=signed-integer-overflow
. There's no unsigned version
Since the code I'm working with compiles as C or C++, and the types in question are all typedefs (which are easily retargeted to classes), it occurs to me that a C++ solution is possible. The following code sample hints at the idea:
#include <inttypes.h>
template <typename outer, typename inner, typename underlying> class arith {
public:
underlying val;
arith(underlying v) : val(v) { }
explicit operator underlying () const { return val; }
outer operator +(const inner &rhs) { return val + rhs.val; }
};
struct narrow;
struct narrow_result : public arith<narrow_result, narrow_result, uint32_t> {
narrow_result(uint32_t v) : arith(v) { }
narrow_result(const narrow &v);
};
struct narrow : public arith<narrow_result, narrow, uint32_t> {
narrow(uint32_t v) : arith(v) { }
narrow(const narrow_result &v) : arith(v.val) { }
};
inline narrow_result::narrow_result(const narrow &v)
: arith(v.val)
{
}
struct wide {
uint64_t val;
wide(uint64_t v) : val(v) { }
wide(const narrow &v) : val(v) { }
operator uint64_t () const { return val; }
wide operator +(const wide &rhs) { return val + rhs.val; }
};
int main()
{
narrow a = 42;
narrow b = 9;
wide c = wide(a) + b;
wide d = a + b; // line 43
narrow e = a + b;
wide f = a; // line 45
narrow g = a + b + b; // line 46
return 0;
}
Here, GNU C++ diagnoses only line 43:
overflow.cc: In function ‘int main()’:
overflow.cc:43:16: error: conversion from ‘narrow_result’ to non-scalar type ‘wide’ requested
Note that a narrow
to wide
implicit conversion is still allowed, as seen in line 45, simply because wide
has a conversion constructor targeting narrow
directly. It just lacks one for narrow_result
.
Line 46 shows that we can compound the arithmetic operations. This is possible because narrow
implicitly converts to narrow_result
and vice versa. However, this implicit conversion doesn't kick in on line 45; the narrow_result
of the addition doesn't convert to narrow
so that this could then convert to wide
.
This can all be wrapped with #ifdef __cplusplus
and the presence of a conditional debug macro, that same macro also enabling alternative definitions of the types as typedefs for narrow
and wide
. Of course, numerous other arithmetic operations must be supported in the arith
template base.