Using the Optimizer to Prove Safety Properties at Compile Time
Martin Uecker, 2026-05-22
Introduction
Previously, when I discussed maybe, I pointed
out that the optimizer can be used to prove that there is no undefined behavior
related to integer operations (or array bounds) left in some function. The idea is
to use the undefined behavior sanitizer in trapping mode to instrument the code
with run-time checks, and then verify that the optimizer eliminates the checks
because they can not be triggered under given preconditions.
Unfortunately, it is not to practical to inspect the assembly code for larger
programs. Here, I want to share a trick which works with GCC to transform
those run-time checks into compile-time warnings or errors.
As an example, we consider a bug related to the rules for integer promotion
which affected a crypto library as discussed
here.
The issue in the following code is that the unsigned eight-bit integer with all
bits set gets promoted to an signed integer. If the integer type has 32 bits,
shifting left by 24 bits shifts into the sign bit which has undefined behavior.
uint8_t start = -1;
printf("%x\n", start); // prints 0xff
uint64_t result = start << 24;
printf("%llx\n", result); // should print 00000000ff000000, but will print ffffffffff000000
result = (uint64_t)start << 24;
printf("%llx\n", result); // prints 00000000ff000000
We can use the shift sanitizer to detect this problem at run-time
(Example on Godbolt). This
is intended to help with finding bugs during testing and can be very effective
whe used with fuzzing. Sanitizers can also be used to safely avoid the undefined
behavior at run-time when used with trapping to terminate the program. Still,
it would be much better (and sometimes this is a requirement) to make sure
already during development that the code is statically safe and can never
fail at run-time.
Amazingly, when using GCC prefixing the code with the declaration
[[gnu::warning("UNSAFE")]] void __builtin_trap();
achieves just this! For the example above, this then produces the following
output (Example on Godbolt).
: In function 'main':
:15:29: warning: call to '__builtin_trap' declared with attribute warning: UNSAFE [-Wattribute-warning]
15 | uint64_t result = start << 24;
Why does this work? The sanitzer inserts calls to the built-in function
__builtin_trap. For some reason, it is possible to add an
explicit declatation to this function and an
attribute
can then be used to tell the compiler to emit a warning (or error) whenever
a call to the function is not eliminated by an optimization.
The example for maybe modified to use this trick
can be found here.
Unfortunately,
this warning can not be controlled at a fine level and also does not provide
too much information. Instrumented code is also sometimes still poorly
optimized on GCC, which makes this less effective than it could be.
Nevertheless, this can be quite useful already, and hopefully GCC's support
for this technique will be improved in the future.
BTW: For those confused about C's conversion rules, it might be helpful to study
some macros that implement the logic
here
for integer promotion and
here
for the usual arithmetic conversions, repectively.