I just spent about 15 minutes working on a problem where an unsigned subtraction converted to a float caused a totally unexpected value.
I had to printf debug this, because it was happening only at a certain point.
The code was something like this:
foreach(i, v; messages) // messages is a string array
{
float vpos = 600 - 30 * (messages.length - i);
DrawText(messages.ptr, 100, vpos.to!int, 20, Colors.WHITE);
}
This is raylib code, and what ends up happening is that once messages.length exceeds 20, you get into a situation where 600 - 30 * (messages.length - i)
wraps to ulong.max - 29. And obviously, that doesn't convert to an int.
So the diabolical thing about this, is that I focused on the messages.length - i
because that is unsigned math, but there is zero chance this could wrap. So I was puzzled how this could be causing overflow.
But the unsigned-ness percolates throughout the whole expression!
I was brainstorming how we could flag this as error prone without flooding all projects with warnings that won't (usually) be a problem.
I thought of this possible filter:
- Unsigned subtraction happens
- The result is used as a signed number
- The signed number's range encloses the range of all values of the unsigned value.
So that last point might seem puzzling, why do we not care about converting signed/unsigned integers of the same width? We don't care because actually it works out exactly as expected:
e.g.
uint x = 100;
int v = x - 200;
assert(v == -100); // OK!
But if you assign to a type which actually can represent all the uint (or at least the range of uint), then you get bombs:
int x = 100;
double v = x - 200;
assert(v == -100); // BOOM
long v2 = x - 200;
assert(v2 == -100); // BOOM
So why not just flag all unsigned/signed conversions? The problem with this idea (and technically it is a sound idea) is that it will flag so many things that are likely fine. Flagging all conversions just is going to be extremely noisy, and require casts everywhere. Subtraction is where the problems are.
In cases where the compiler knows that unsigned subtraction won't wrap, it can also skip the warning.
Thoughts?
-Steve