Introduction and Background
tl;dr: I have a tree that implements the “wraps” attribute.
This attribute would allow for more granular control over what expressions can emit integer overflow warnings or integer overflow sanitizer errors.
Currently, you cannot attach __attribute__((no_sanitize("signed-integer-overflow")))
to variable or type declarations. If you wanted to disable signed or unsigned integer overflow checks you’d have to use the aforementioned attribute as a function attribute, thus disabling instrumentation for the entire function. This works but is not granular enough for many cases. Another (and more granular) solution is to use builtins to intentionally perform wrapping arithmetic. However, in some codebases, refactoring existing arithmetic to utilize these overflow builtins may only serve to complicate code. For example, the Linux Kernel has, like lots of other projects, some agreed upon idioms which are understood by its developers and changing these patterns is frowned upon:
“if there’s some unsigned wrap-around checker that doesn’t
understand this traditional way of doing overflow checking, that piece
of crap needs fixing.” - Linus
This was in response to a patch that was trying to change the commonly accepted idiom: base + offset < base
to utilize a builtin (via a macro) to silence sanitizer errors.
Recently, there’s been some effort by Kees, myself and others to reintroduce the signed and unsigned integer overflow sanitizers in the Linux Kernel. Upon turning these sanitizers back on (or for the case of signed-integer-overflow, making it work at all) we encountered plently of existing instances of integer overflow in the Kernel. However, there’s some pushback when trying to alter the “traditional” way of doing things.
With this new wrapping attribute we can specifically craft types that disable overflow instrumentation, without modifying traditional and widely understood code patterns.
It should be noted that constant expressions containing a wrapping type or variable should not result in -Winteger-overflow
warnings either.
Examples
When compiling with -fsanitize=signed-integer-overflow,unsigned-integer-overflow
:
typedef int __attribute__((wraps)) wrapping_int;
wrapping_int A = INT_MAX;
++A; /* This will not trigger a runtime sanitizer error */
unsigned long addr __attribute__((wraps)); /* wraps can be added to variables */
unsigned long size; /* both operands don't need wraps, just one */
...
if (addr + size < addr) {
// handle overflow gracefully, because the overflow sanitizer won't trip
}
...
typedef int __attribute__((wraps)) wrapping_int;
int a = (wrapping_int) INT_MAX + 1; // no sanitizer trip (or -Winteger-overflow)
The next few examples will show some cases where sanitizers will still trip:
wrapping_int A = INT_MAX;
int B = 1;
A = INT_MAX + 1; // overflow sanitizer trips! (INT_MAX + 1) is computed and trips the sanitizer before assignment to A
B = (INT_MAX+B) * A; // overflow sanitizer trips!
B = INT_MAX + B * A; // overflow sanitizer does NOT trip!
// This is because B * A is computed first, the result type carries the wraps attribute
// with it from A, then that result is used in summation with INT_MAX.
To summarize this behavior:
Using __attribute__((wraps))
on a typedef or variable declaration makes it a “wrapping” type or variable thereby disabling overflow instrumentation for either 1) arithmetic performed directly on wrapping variables or types or 2) arithmetic performed on the result of calculations containing a wrapping variable or type. Instrumentation is not disabled for calculations containing subexpressions wherein no wrapping variables are present.
Other Notes
-
[[wraps]]
and[[clang::wraps]]
are supported for C++11 -
The wraps attribute cannot be applied to functions
-
The wraps attribute can be applied to member variables
-
The wraps attribute can still be used even when the sanitizers and warnings
are disabled, at this point it’s just an annotation to readers of the code that
arithmetic may wraparound, expectedly so. -
No documentation or full frontend diagnostics yet – I’m waiting to hear back from folks regarding this RFC