How to obtain warning for forgotten cast in arithmetic? - c++

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 the C or C++ front-end of GCC (or of any other compiler) to warn about this situation: that an operation is being done in a narrow type that is immediately widened?
I've read through the current GCC documentation, and tried various warnings like -Wconversion, but nothing.

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.

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.

Since any other compiler is OK, you can use
Visual Studio Code Analysis which does a static compile time check
Clang's -fsanitize=unsigned-integer-overflow option for runtime check
Visual Studio Code Analysis can do that
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

Related

C++ warn when storing 32 bit value in a 64 bit variable

I recently discovered a hard to find bug in a project I am working on. The problem was that we did a calculation that had a uint32_t result and stored that in a uint64_t variable. We expected the result to be a uint64_t because we knew that the result can be too big for a 32 bit unsigned integer.
My question is: is there a way to make the compiler (or a static analysis tool like clang-tidy) warn me when something like this happens?
An example:
#include <iostream>
constexpr uint64_t MUL64 { 0x00000000ffffffff };
constexpr uint32_t MUL32 { 0xffffffff };
int main() {
const uint32_t value { 0xabababab };
const uint64_t value1 { MUL64 * value }; // the result is a uint64_t because
// MUL64 is a uint64_t
const uint64_t value2 { MUL32 * value }; // i'd like to have a warning here
if (value1 == value2) {
std::cout << "Looks good!\n";
return EXIT_SUCCESS;
}
std::cout << "Whoopsie\n";
return EXIT_FAILURE;
}
Edit:
The overflow was expected, i.e. we knew that we would need an uint64_t to store the calculated value. We also know how to fix the problem and we changed it later to something like:
const uint64_t value2 { static_cast<uint64_t>(MUL32) * value };
That way the upper 32 bits aren't cut off during the calculation. But things like that may still happen from time to time, and I just want to know whether there is way to detect this kind of mistakes.
Thanks in advance!
Greetings,
Sebastian
The multiplication behavior for unsigned integral types is well-defined to wrap around modulo 2 to the power of the width of the integer type. Therefore there isn't anything here that the compiler could be warning about. The behavior is expected and may be intentional. Warning about it would give too many false positives.
Also, in general the compiler cannot test for overflow at compile-time outside a constant expression evaluation. In this specific case the values are obvious enough that it could do that though.
Warning about any widening conversion after arithmetic would very likely also be very noisy.
I am not aware of any compiler flag that would add warnings for the reasons given above.
Clang-tidy does have a check named bugprone-implicit-widening-of-multiplication-result specifically for this case of performing a multiplication in a narrower type which is then implicitly widened. It seems the check is present since LLVM 13. I don't think there is an equivalent for addition though.
This check works here as expected:
<source>:11:29: warning: performing an implicit widening conversion to type 'const uint64_t' (aka 'const unsigned long') of a multiplication performed in type 'unsigned int' [bugprone-implicit-widening-of-multiplication-result]
const uint64_t value2 { MUL32 * value }; // i'd like to have a warning here
^
<source>:11:29: note: make conversion explicit to silence this warning
const uint64_t value2 { MUL32 * value }; // i'd like to have a warning here
^~~~~~~~~~~~~
static_cast<const uint64_t>( )
<source>:11:29: note: perform multiplication in a wider type
const uint64_t value2 { MUL32 * value }; // i'd like to have a warning here
^~~~~
static_cast<const uint64_t>()
Clang's undefined behavior sanitizer also has a check that flags all unsigned integer overflows at runtime, which is not normally included in -fsanitize=undefined. It can be included with -fsanitize=unsigned-integer-overflow. That will very likely require adding suppressions for intended wrap-around behavior. See https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html for details.
It seems however that this check isn't applied here since the arithmetic is performed by the compiler at compile-time. If you remove the const on value2, UBSan does catch it:
/app/example.cpp:11:29: runtime error: unsigned integer overflow: 4294967295 * 2880154539 cannot be represented in type 'unsigned int'
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior /app/example.cpp:11:29 in
Whoopsie
GCC does not seem to have an equivalent option.
If you want consistent warnings for overflow in unsigned arithmetic, you need to define your own wrapper classes around the integer types that perform the overflow check and e.g. throw an exception if it fails or alternatively you can implement functions for overflow-safe addition/multiplication which you would then have to use instead of the + and * operators.

Compiler warning (or static analysis) for subtraction of unsigned integers?

Consider the following program:
#include <iostream>
int main()
{
unsigned int a = 3;
unsigned int b = 7;
std::cout << (a - b) << std::endl; // underflow here!
return 0;
}
In the line starting with std::cout an underflow is happening because a is lesser than b so a-b is less than 0, but since a and b are unsigend so is a-b.
Is there a compiler flag (for G++) that gives me a warning when I try to calculate the difference of two unsigend integers?
Now, one could argue that an overflow/underflow can happen in any calculation using any operator. But I think it is more dangerous to apply operator - to unsigend ints because with unsigned integers this error may happen with quite low (to me: "more common") numbers.
A (static analysis) tool that finds such things would also be great but I much prefer a compiler flag and warning.
GCC does not (afaict) support it, but Clang's UBSanitizer has the following option [emphasis mine]:
-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

Why can't I use a class which implements operator unsigned int() as an array index in Visual C++ 32-bit?

We have an emulated unsigned integer type, let's call it uint_t, which has operator unsigned int() implemented.
When we use a variable of this type as an array index:
#pragma warning(disable: 4514) // unreferenced inline function removed
#pragma warning(disable: 4710) // function not inlined
#pragma warning(disable: 4711) // function selected for inline expansion
#include <cstdio>
class my_uint_t
{
public:
my_uint_t(unsigned int val) { m_val = val; }
operator unsigned int() const { return m_val; }
unsigned int operator ++(int) { return m_val++; }
private:
unsigned int m_val;
};
int main(int, const char **)
{
const char *myArray[] = { "1", "2", "3", "4", "5" };
for (my_uint_t i = 0; i < sizeof(myArray)/sizeof(myArray[0]); i++)
{
printf("%s\n", myArray[i]);
}
for (unsigned int i = 0; i < sizeof(myArray)/sizeof(myArray[0]); i++)
{
printf("%s\n", myArray[i]);
}
return 0;
}
and compile it in Visual C++ 2015 x86 with all warnings enabled, we get this warning:
warning C4365: 'argument': conversion from 'unsigned int' to 'int', signed/unsigned mismatch
According to the C++ language spec (from http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4659.pdf)
8.2.1 Subscripting
1 A postfix expression followed by an expression in square brackets is a postfix expression. One of the
expressions shall be a glvalue of type “array of T” or a prvalue of
type “pointer to T” and the other shall be a prvalue of unscoped
enumeration or integral type. The result is of type “T”. The type “T”
shall be a completely-defined object type.66 The expression E1[E2] is
identical (by definition) to *((E1)+(E2))...
Why does Visual C++ not accept a class with a single cast operator to unsigned int as an "integral type"?
Note: it is happy with the line using a real unsigned int, but not with the class with the unsigned int operator.
Note also: this only happens in x86 (32-bit) builds, not x64.
Out of curiosity, I've tried that on my own:
#include <iostream>
#include <vector>
struct UInt {
unsigned value;
UInt(unsigned value = 0): value(value) { }
UInt(const UInt&) = default;
operator unsigned int () const { return value; }
};
int main()
{
UInt i(2);
int a[] = { 0, 1, 2, 3, 4, 5 };
std::cout << "a[UInt(2)]: " << a[i] << '\n';
std::vector<int> v(std::begin(a), std::end(a));
std::cout << "v[UInt(2)]: " << v[i] << '\n';
return 0;
}
Output:
a[UInt(2)]: 2
v[UInt(2)]: 2
This was tested on coliru.com: Live Demo on coliru.
As OP mentioned Visual C++, I did the same test in VS2013 (64 bit) and got the exact equal output.
The warning 4365 didn't appear. (I switched to /Wall and got tons of warnings about std headers which I've never seen before.)
Please, note, that I get warning 4365 with the following change:
struct UInt {
int value;
UInt(int value = 0): value(value) { }
UInt(const UInt&) = default;
operator unsigned int () const { return value; }
};
and this is caused by operator unsigned int () which now implicitly casts int value to unsigned int – a reasonable warning. However, the program prints again the same output (as expected).
As suggested by OP, I compiled the upper sample again in VS2013 but this time for 32 bit.
Now, I get the warning 4365 for the array subscript a[i].
There is no complaint about the vector subscript v[i].
I'm not sure whether it is worth to consider this warning for the array subscript too seriously. I mean it's a warning but no error.
I don't think that warnings shall be ignored in general (They shouldn't!) but in this specific case...
Btw. I digged a bit to deeper concerning std::vector::operator[]:
The const and non-const operator[] are declared with argument size_type as expected. I tried to fiddle out where size_type is actually defined but I got lost in the templates with templates... I expect that it is finally a typedef of std::size_t (the unsigned integer type of the result of the sizeof operator). So, of course, no warning 4365.
The operator[] for C arrays and raw pointers is hard to check as it's built-in the compiler. May be, you ask in MSDN regarding this...
May be, it's worth to check this in VS2017 before posting a bug report to MS. ;-)
Consensus seems to be that this should work and hence is a bug in the Visual C++ compiler for x86. I have reported it to Microsoft at https://developercommunity.visualstudio.com/content/problem/322701/cant-use-an-emulated-uint-type-as-an-array-subscri.html
I still see it with Visual Studio 2017 as well as Visual Studio 2015.
It builds perfectly with gcc.
The warning is generally useless, but the compiler is allowed to emit useless warnings about absolutely anything, as long as it still accepts valid programs.
The uselessness of the warning is an indication of a compiler bug.
Note there's no inconsistency between 32-bit and 64-bit versions, which can be seen by replacing unsigned int with size_t. With this change, both 32-bit and 64-bit versions will emit the same kind of useless warning.

Does an observable difference exist using `unsigned long` and `unsigned int` in C (or C++) when both are 32 bits wide?

I'm using an MPC56XX (embedded systems) with a compiler for which an int and a long are both 32 bits wide.
In a required software package we had the following definitions for 32-bit wide types:
typedef signed int sint32;
typedef unsigned int uint32;
In a new release this was changed without much documentation to:
typedef signed long sint32;
typedef unsigned long uint32;
I can see why this would be a good thing: Integers have a conversion rank between short and long, so theoretically extra conversions can apply when using the first set of definitions.
My question: Given the above change forced upon us by the package authors, is there a situation imaginable where such a change would change the compiled code, correctly leading to a different result?
I'm familiar with the "usual unary conversions" and the "usual binary conversions", but I have a hard time coming up with a concrete situation where this could really ruin my existing code. But is it really irrelevant?
I'm currently working in a pure C environment, using C89/C94, but I'd be interested in both C and C++ issues.
EDIT: I know that mixing int with sint32 may produce different results when it's redefined. But we're not allowed to use the original C types directly, only the typedef'ed ones.
I'm looking for a sample (expression or snippet) using constants, unary/binary operators, casts, etc. with a different but correct compilation result based on the changed type definition.
In C++ you may run into issues with function overloading. Say you had the following:
signed int func(signed int x) {
return x + 1;
}
signed long func(signed long x) {
return x - 1;
}
int main(void) {
sint32 x = 5;
std::cout << func(x) << std::endl;
}
Prior to the typedef definition change, the value 6 would be printed. After the change the value 4 would be printed. While it's unlikely that an overload would have behavior that's this different, it is a possibility.
You could also run into issues with overload resolution. Assume you had two functions with the following definitions:
void func(int x);
void func(unsigned int x);
and were calling the functions with:
sint32 x;
func(x);
Prior to the change, the function call was unambiguous, func(int) would be an exact match. After the typedef change, there is no longer an exact match (neither function takes a long), and the compiler fails since it will not be able to determine which overload to invoke.
It might lead to subtle issues because literal numbers are int by default.
Consider the following program:
#include <iostream>
typedef signed short old16;
typedef signed int old32;
void old(old16) { std::cout << "16\n"; }
void old(old32) { std::cout << "32\n"; }
typedef signed short new16;
typedef signed long new32;
void newp(new16) { std::cout << "16\n"; }
void newp(new32) { std::cout << "32\n"; }
int main() {
old(3);
newp(3); // expected-error{{call of overload ‘newp(int)’ is ambiguous}}
}
This leads to an error because the call to newp is now ambiguous:
prog.cpp: In function ‘int main()’:
prog.cpp:17: error: call of overloaded ‘newp(int)’ is ambiguous
prog.cpp:12: note: candidates are: void newp(new16)
prog.cpp:13: note: void newp(new32)
whereas it worked fine before.
So there might be some overloads surprises where literals were used. If you always use named (and thus typed) constants, you should be fine.
If a pointer to sint32/uint32 is used where a pointer to int/long is expected (or vice versa) and they don't match int with int or long with long, you may get a warning or error at compile time (may in C, guaranteed in C++).
#include <limits.h>
#if UINT_MAX != ULONG_MAX
#error this is a test for systems with sizeof(int)=sizeof(long)
#endif
typedef unsigned uint32i;
typedef unsigned long uint32l;
uint32i i1;
uint32l l1;
unsigned* p1i = &i1;
unsigned long* p1l = &l1;
unsigned* p2il = &l1; // warning or error at compile time here
unsigned long* p2li = &i1; // warning or error at compile time here
int main(void)
{
return 0;
}
Nothing in the Standard would allow code to safely regard a 32-bit int and long as interchangeable. Given the code:
#include <stdio.h>
typedef int i32;
typedef long si32;
int main(void)
{
void *m = calloc(4,4); // Four 32-bit integers
char ch = getchar();
int i1 = ch & 3;
int i2 = (ch >> 2) & 3;
si32 *p1=(si32*)m + i1;
i32 *p2=(i32*)m + i2;
*p1 = 1234;
*p2 = 5678;
printf("%d", *p1);
return 0;
}
A compiler would be entitled to assume that because p1 and p2 are declared as different types (one as int and the other long), they cannot possibly point to the same object (without invoking Undefined Behavior). For any input character were the above program would be required to do anything (i.e. those which would avoid Undefined Behavior by causing i1 and i2 to be unequal), the program would be required to output 1234. Because of the Strict Aliasing Rule, a compiler would be entitled to do anything it likes for characters like 'P', 'E', 'J', or 'O' which would cause i and j to receive matching values; it could thus output 1234 for those as well.
While it's possible (and in fact likely) that many compilers where both int and long are 32 bits will in fact regard them as equivalent types for purposes of the Strict Aliasing Rule, nothing in the Standard mandates such behavior.

C++ Integer overflow problem when casting from double to unsigned int

I need to convert time from one format to another in C++ and it must be cross-platform compatible. I have created a structure as my time container. The structure fields must also be unsigned int as specified by legacy code.
struct time{
unsigned int timeInteger;
unsigned int timeFraction;
} time1, time2;
Mathematically the conversion is as follows:
time2.timeInteger = time1.timeInteger + 2208988800
time2.timeFraction = (time1.timeFraction * 20e-6) * 2e32
Here is my original code in C++ however when I attempt to write to a binary file, the converted time does not match with the truth data. I think this problem is due to a type casting mistake? This code will compile in VS2008 and will execute.
void convertTime(){
time2.timeInteger = unsigned int(time1.timeInteger + 2209032000);
time2.timeFraction = unsigned int(double(time1.timeFraction) * double(20e-6)*double(pow(double(2),32)));
}
Just a guess, but are you assuming that 2e32 == 2^32? This assumption would make sense if you're trying to scale the result into a 32 bit integer. In fact 2e32 == 2 * 10^32
Slightly unrelated, I think you should rethink your type design. You are basically talking about two different types here. They happen to store the same data, albeit in different results.
To minimize errors in their usage, you should define them as two completely distinct types that have a well-defined conversion between them.
Consider for example:
struct old_time {
unsigned int timeInteger;
unsigned int timeFraction;
};
struct new_time {
public:
new_time(unsigned int ti, unsigned int tf) :
timeInteger(ti), timeFraction(tf) { }
new_time(new_time const& other) :
timeInteger(other.timeInteger),
timeFraction(other.timeFraction) { }
new_time(old_time const& other) :
timeInteger(other.timeInteger + 2209032000U),
timeFraction(other.timeFraction * conversion_factor) { }
operator old_time() const {
old_time other;
other.timeInteger = timeInteger - 2209032000U;
other.timeFraction = timeFraction / conversion_factor;
return other;
}
private:
unsigned int timeInteger;
unsigned int timeFraction;
};
(EDIT: of course this code doesn’t work for the reasons pointed out below.
Now this code can be used frictionless in a safe way:
time_old told; /* initialize … */
time_new tnew = told; // converts old to new format
time_old back = tnew; // … and back.
The problem is that (20 ^ -6) * (2 e32) is far bigger than UINT_MAX. Maybe you meant 2 to the power of 32, or UINT_MAX, rather than 2e32.
In addition, your first line with the integer, the initial value must be less than (2^32 - 2209032000), and depending on what this is measured in, it could wrap round too. In my opinion, set the first value to be a long long (normally 64bits) and change 2e32.
If you can't change the type, then it may become necessary to store the field as it's result in a double, say, and then cast to unsigned int before use.