GCC and -Wconversion - c++

Let's compile the following program:
int main()
{
uint16_t data = 0;
data |= uint16_t(std::round(3.14f));
return 0;
}
with g++ -Wconversion prog.cpp
We'll get warning: conversion to ‘uint16_t {aka short unsigned int}’ from ‘int’ may alter its value, but I can't see implicit conversions here.
This kind of warnings should be muted by explicit casts, for example:
double d = 3.14;
float foo1 = d; // Warning
float foo2 = float(d); // No warning
float foo2 = static_cast<float>(d); // No warning
Is GCC right here or it's a bug?
Note that my snippet is minimal. For example, warning disappears in following cases:
remove the f suffix from 3.14, i.e. make it double
use assignment instead of |=
remove std::round
cache rounding result: const auto r = uint16_t(std::round(3.14f));, then or-assign it to data.

Is GCC right here or it's a bug?
Since the behavior does not match the expectation, I'd call it a bug.
From https://godbolt.org/z/aSj--7, it seems that in GCC's eye, data |= uint16_t(std::round(3.14f)) is translated as
(void) (data = TARGET_EXPR <D.2364, (uint16_t) round (3.1400001049041748046875e+0)>;, data | NON_LVALUE_EXPR <D.2364>;)
(TARGET_EXPR represents a temporary object. D.2364 is an internal variable name.)
Translate GCC's internal language back to C++, and we'll get
data = (temp = (uint16_t) round (3.14e+0), data | temp)
Since the LHS of the comma expression does not affect the RHS, this should be as safe as data = data | temp. However, GCC warns at the former but not at the latter, which is very unlikely to be intentional. Thus I believe that it is an oversight of GCC maintainers.

The warning is bogus.
According to [over.built]/22:
For every triple (L, VQ, R), where L is an integral type, VQ is either volatile or empty, and R is a promoted integral type, there exist candidate operator functions of the form ...
VQ L& operator|=(VQ L&, R);
So we get a built-in unsigned short operator |=(unsigned short&, unsigned int);
There are no implicit conversions in the given expression
uint16_t data = 0;
data |= uint16_t(std::round(3.14f));

Related

How to obtain warning for forgotten cast in arithmetic?

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

Bit shift leads to strange type conversion

The following code compiles without warning:
std::uint16_t a = 12;
std::uint16_t b = a & 0x003f;
However, performing a bit shift along with the bitwise and leads to an 'implicit cast warning':
std::uint16_t b = (a & 0x003f) << 10; // Warning generated.
Both gcc and clang complain that there is an implicit conversion from int to uint16_t, yet I fail to see why introducing the bit shift would cause the right hand expression to suddenly evaluate to an int.
EDIT: For clang, I compiled with the -std=c++14 -Weverything flags; for gcc, I compiled with the -std=c++14 -Wall -Wconversion flags.
From cppreference.com: "If the operand passed to an arithmetic operator is integral or unscoped enumeration type, then before any other action (but after lvalue-to-rvalue conversion, if applicable), the operand undergoes integral promotion."
For instance:
byte a = 1;
byte b = a << byte(1);
a and 1 are promoted to int: int(a) and int(byte(1)).
a is shifted one position to left: int result = int(a) << int(byte(1)) (the result is an int).
result is stored in b. Since int is wider than byte an warning will be issued.
If the operands are constant expressions, a compiler might be able to compute the result at compile time and issue an warning iff the result does not fit in destination:
byte b = 1 << 1; // no warning: does not exceed 8 bits
byte b = 1 << 8; // warning: exceeds 8 bits
Or, using constexpr:
constexpr byte a = 1;
byte b = a << 1; // no warning: it fits in 8 bits
byte b = a << 8; // warning: it does not fit in 8 bits
Doing any arithmetic with integer types always is preceded by promotion to at least (sometimes, but not in this case using gcc, unsigned) int. As you can see by this example, this applies to you first, warning-less variant too.
The probably best way to get around these (admittedly often surprising) integer promotion rules would be to use an unsigned int (or uint32_t on common platforms) from the get go.
If you cannot or don't want to use the bigger type, you can just static_cast the result of the whole expression back to std::uint16_t:
std::uint16_t b = static_cast<std::uint16_t>((a & 0x003f) << 10);
This will correctly result in the RHS-value mod 2^16.
yet I fail to see why introducing the bit shift would cause the right hand expression to suddenly evaluate to an int.
I think you misinterpret the warning. In both cases expression evaluate to int but in the first case result will always fit in uint16_t, in the second case it will not. Looks like the compiler is smart enough to detect that and generates warning only in the second case.

C++ implicit numeric type demoting

Recently, I have noticed that C/C++ seems to be very permissible with numeric type conversion, as it implicitly casts a double to int.
Test:
Environment: cpp.sh, Standard C++ 14, Compilation warnings all set
Code:
int intForcingFunc(double d) {
return d; // this is allowed
}
int main() {
double d = 3.1415;
double result = intForcingFunc(d);
printf("intForcingFunc result = %f\n", result);
int localRes = d; // this is allowed
printf("Local result = %d\n", localRes);
int staticCastRes = static_cast<int>(d); // also allowed
printf("Static cast result = %d\n", staticCastRes);
}
No warnings are issues during compilation.
Documentation mentions tangentially the subject, but misses the exact case of the question:
C++ is a strong-typed language. Many conversions, specially those that imply a different interpretation of the value, require an explicit conversion, known in C++ as type-casting.
I have also tried in a managed language (C#) and all these cases are not allowed (as expected):
static int intForcingFunc(double d)
{
// Not legal: Cannot implicitly convert type 'double' to 'int'
// return d;
return Convert.ToInt32(d);
}
static void Main(string[] args)
{
double d = 3.1415;
double result = intForcingFunc(d);
Console.WriteLine("intForcingFunc result = " + result);
// Not legal: Cannot implicitly convert type 'double' to 'int'
// int localRes = d;
int localRes = (int)d;
Console.WriteLine("local result = " + result);
Console.ReadLine();
}
Why is this behavior allowed in a strong-typed language? In most cases, this is undesired behavior. One reason behind this seems to be lack of arithmetic overflow detection.
Unfortunately, this behavior is inherited from C, which notoriously "trusts the programmer" in these things.
The exact warning flag for implicit floating-point to integer conversions is -Wfloat-conversion, which is also enabled by -Wconversion. For some unknown reason, -Wall, -Wextra, and -pedantic (which cpp.sh provides) don't include these flags.
If you use Clang, you can give it -Weverything to enable literally all warnings. If you use GCC, you must explicitly enable -Wfloat-conversion or -Wconversion to get a warning when doing such conversions (among other useful flags you will want to enable).
If you want, you can turn it to an error with e.g. -Werror-conversion.
C++11 even introduced a whole new safer initialization syntax, known as uniform initialization, which you can use to get warnings for the implicit conversions in your example without enabling any compiler warnings:
int intForcingFunc(double d) {
return {d}; // warning: narrowing conversion of 'd' from 'double' to 'int' inside { }
}
int main() {
double d{3.1415}; // allowed
int localRes{d}; // warning: narrowing conversion of 'd' from 'double' to 'int' inside { }
}
You did not specify what compiler you are working with, but you probably have not, in fact, enabled all warnings. There's a story behind it, but the net effect is that g++ -Wall does not actually enable all warnings (not even close). Others (eg. clang++), in order to be drop-in replacement-compatible with g++ must do the same.
Here is a great post on setting strong warnings for g++: https://stackoverflow.com/a/9862800/1541330
If you are using clang++, things will be much easier for you: try using -Weverything. It does just what you expect (turns on every warning). You can add -Werror and the compiler will then treat any warnings that occur as compile-time errors. If you are now seeing warnings (errors) that you want to suppress, just add -Wno-<warning-name> to your command (eg. -Wno-c++98-compat).
Now the compiler will warn you whenever an implicit narrowing conversion (conversion with possible data loss that you didn't explicitly ask for) occurs. In cases where you want a narrowing conversion to occur, you must use an appropriate cast, eg:
int intForcingFunc(double d) {
return static_cast<int>(d); //cast is now required
}

Compiler warnings conversion

We are compiling using gcc with -Wconversion enabled. I get following warnings when I left shift result returned by isBitSet function below.
warning: conversion to 'u_int16_t {aka short unsigned int}' from 'int'
may alter its value [-Wconversion]
#include <stdint.h>
using namespace std;
void convertToPos(uint16 in) {
auto isBitSet = [&in](uint8_t position) -> bool{
return (in & (1 << position));
};
uint16_t signal = 0;
signal |= isBitSet(1) << 9; // Calibrated
signal |= isBitSet(2) << 10; // Corresponds to digital map
signal |= isBitSet(5) << 13; // DR
signal |= isBitSet(8) << 15; // 3D Fix
}
int main(int argc)
{
convertToPos(4);
return 0;
}
I tried changing lambda to below, but still I get the same error. How can I fix it?
auto isBitSet = [&in](uint8_t position) -> uint16_t {
return (in & (1 << position)) ? 1u:0u;
};
To avoid the warning, you may do:
signal = uint16_t(signal | isBitSet(1u) << 9);
as operator | promotes operator uint16_t to int.
In this case both shift operators and bitwise inclusive or operator apply the integer promotions to their operands and since uint16_t can be represented by int the operands are promoted to int and therefore gcc is warning you that the conversion from a larger type int to uint16_t may result in loss.
The C++ way to deal with this is to use static_cast, it looks like the following works:
signal = signal | static_cast<uint16_t>(isBitSet(1) << 9);
it looks like it is not neccessary to cast the result of |, although strictly it should be no different than << but I guess gcc is able to deduce that it is okay in this case. Technically this is better:
signal = static_cast<uint16_t>( signal | isBitSet(1) << 9);
For reference the draft C++ standard in 5.8 Shift operators says:
The operands shall be of integral or unscoped enumeration type and
integral promotions are performed.[...]
and section 5.13 Bitwise inclusive OR operator says:
The usual arithmetic conversions are performed;[...]
the usual arithmetic conversions in this case ends up applying the integer promotions.
It's not an error. What it's saying is that by converting from an integer to an unsigned integer, the value will change if the integer is negative. There's really no "fix" for this; you just need to be careful what values the unsigned is taking from the signed. I'm guessing you're using unsigned because it needs to be 0 and above, so if anything, this will fix any accidentally-entered negative values.

Locating numerical errors due to Integer division

Is there a g++ warning or other tool that can identify integer division (truncation toward zero)? I have thousands of lines of code with calculations that inevitably will have numerical errors typically due to "float = int/int" that need to be located. I need a reasonable method for finding these.
Try -Wconversion.
From gcc's man page:
Warn for implicit conversions that may
alter a value. This includes
conversions between real and integer,
like "abs (x)" when "x" is "double";
conversions between signed and
unsigned, like "unsigned ui = -1"; and
conversions to smaller types, like
"sqrtf (M_PI)". Do not warn for
explicit casts like "abs ((int) x)"
and "ui = (unsigned) -1", or if the
value is not changed by the conversion
like in "abs (2.0)". Warnings about
conversions between signed and
unsigned integers can be disabled by
using -Wno-sign-conversion.
For C++, also warn for conversions
between "NULL" and non-pointer types;
confusing overload resolution for
user-defined conversions; and
conversions that will never use a type
conversion operator: conversions to
"void", the same type, a base class or
a reference to them. Warnings about
conversions between signed and
unsigned integers are disabled by
default in C++ unless
-Wsign-conversion is explicitly enabled.
For the following sample program (test.cpp), I get the error test.cpp: In function ‘int main()’:
test.cpp:7: warning: conversion to ‘float’ from ‘int’ may alter its value.
#include <iostream>
int main()
{
int a = 2;
int b = 3;
float f = a / b;
std::cout << f;
return 0;
}
I have a hard time calling these numerical errors. You asked for integer calculations, and got the correct numbers for integer calculations. If those numbers aren't acceptable, then ask for floating point calculations:
int x = 3;
int y = 10;
int z = x / y;
// "1." is the same thing as "1.0", you may want to read up on
// "the usual arithmetic conversions." You could add some
// parentheses here, but they aren't needed for this specific
// statement.
double zz = 1. * x / y;
This page contains info about g++ warnings. If you've already tried -Wall then the only thing left could be the warnings in this link. On second look -Wconversion might do the trick.
Note: Completely edited the response.
Remark on -Wconversion of gcc:
Changing the type of the floating point variable from float to double makes the warning vanish:
$ cat 'file.cpp'
#include <iostream>
int main()
{
int a = 2;
int b = 3;
double f = a / b;
std::cout << f;
}
Compiling with $ g++-4.7 -Wconversion 'file.cpp' returns no warnings (as $ clang++ -Weverything 'file.cpp').
Explanation:
The warning when using the type float is not returned because of the totally valid integer arithmetics, but because float cannot store all possible values of int (larger ones cannot be captured by float but by double). So there might be a change of value when assigning RHS to f in the case of float but not in the case of double. To make it clear: The warning is not returned because of int/int but because of the assignment float = int.
For this see following questions: what the difference between the float and integer data type when the size is same in java, Storing ints as floats and Rounding to use for int -> float -> int round trip conversion
However, when using float -Wconversion could still be useful to identify possible lines which are affected but is not comprehensive and is actually not intended for that. For the purpose of -Wconversion see docs/gcc/Warning-Options.html and here gcc.gnu.org/wiki/NewWconversion
Possibly of interest is also following discussion 'Implicit casting Integer calculation to float in C++'
The best way to find such error is to have really good unit tests. All alternatives are not good enough.
Have a look at this clang-tidy detection.
It catches cases like this:
d = 32 * 8 / (2 + i);
d = 8 * floatFunc(1 + 7 / 2);
d = i / (1 << 4);