Clang 8 checks the integer conversions C permits
Clang 8 extends runtime checks for implicit integer conversions and adds automatic variable initialization. Both address mistakes that ordinary compilation can leave intact.
An integer conversion can obey C's rules and still violate the application's assumptions. A negative count passed to an unsigned parameter is a useful example. The compiler has a conversion to perform, but the programmer may have failed to reject an error value. Clang 8 expands its runtime instrumentation to report implicit conversions that change an integer's sign. That gives C programmers a way to examine actual values at these boundaries.
LLVM 8.0.0 was announced on March 20. Its Clang release notes describe the new sign-change check, including support for compound assignments and the remaining gap around bitfields. This is the part of the release that interests me most. C was my first language, and its small syntax does little to reduce the amount a programmer must understand about the values passing through a program.
Checking suspicious arithmetic
The versioned UndefinedBehaviorSanitizer manual defines the sign-change check in terms of the values before and after conversion. A negative original value becoming positive or zero qualifies. So does a positive original value becoming negative. The check concerns a change in meaning at a particular execution point, which makes it useful when a declaration alone does not explain whether a conversion is dangerous.
Consider a function that accepts a count as an unsigned integer. The following program deliberately supplies a negative value through an implicit conversion. It is a small example of the condition the instrumentation is intended to report.
static unsigned int as_count(int value)
{
return value;
}
int main(void)
{
volatile int value = -1;
return as_count(value) == 0;
}
A Clang 8 build can request the specific check and make its report terminate execution. The sanitizer manual documents both the check name and the recovery control used here.
clang -fsanitize=implicit-integer-sign-change \
-fno-sanitize-recover=implicit-integer-sign-change \
conversion.c -o conversion
./conversion
The relevant conversion occurs at the return statement in as_count. The variable's source type is signed and the function's result type is unsigned. Adding an explicit cast would change what this particular implicit-conversion check observes, as the release notes' example explains. A cast therefore needs a justification in the program's contract. It is not evidence that the value was checked.
The sanitizer name deserves attention because its containing tool also checks operations that have defined behaviour. The manual explicitly distinguishes suspicious integer conversions from undefined behaviour. In particular, -fsanitize=undefined does not enable the implicit-conversion group. An existing undefined-behaviour build can consequently miss the very condition shown above, even though the instrumentation is available in the compiler.
The broader -fsanitize=implicit-conversion group enables sign changes together with signed and unsigned integer truncation checks. Truncation instrumentation asks whether converting a narrowed value back to the original width recovers the original value. That is a different question from whether an arithmetic operation itself overflowed. A program can compute a representable value and then lose information while assigning it to a smaller destination.
This separation helps explain why the manual's -fsanitize=integer group contains more than signed overflow detection. It also enables unsigned overflow, shift checks and integer division checks, along with implicit conversion checks. Choosing a group expresses which conditions a test executable should report. The individual check names remain available when a project needs a narrower policy for a particular executable.
Reports need an execution policy
Instrumentation also needs a defined response when it encounters a problem. According to the same manual, the default sanitizer behaviour prints a report and continues. -fno-sanitize-recover requests a report followed by termination. Trap mode instead executes a trap instruction and does not require the UBSan runtime for the trapping checks. These choices affect what a failing test can tell its caller.
For a test executable, continuing after a diagnostic can allow several problems to appear in one run. Terminating at the first report gives the invoking process an immediate failure. Neither changes the conversion's intended application meaning. That still requires a decision about whether negative values are errors, whether narrowing is allowed, and where the program should enforce those conditions.
I value this distinction because C's rules and a system's requirements are different bodies of knowledge. A compiler can identify a change of sign without knowing whether a caller intended modular arithmetic. The report supplies a concrete execution to inspect. It leaves the policy decision with the code that understands the value.
Initialization has a different purpose
Clang 8 also introduces -ftrivial-auto-var-init=pattern. Automatic variables remain uninitialized by default. The new option supplies a pattern to reduce information leaks caused by accidental use of stack or register contents. The release notes explicitly retain caveats for volatile variables, unreachable declarations and incomplete padding coverage.
That option answers a different question from a conversion diagnostic. Pattern initialization changes the contents available to an erroneous read. Conversion instrumentation reports a value change at an operation the program executes. Neither establishes that a count is valid for the allocation or input being processed. Explicit initialization and explicit validation still describe application behaviour more accurately than a build flag can.
The distinction also matters when reading a test result. A run that produces no conversion report says nothing about whether every path executed. A run with pattern initialization says nothing about whether each local variable received the value its author intended. The compiler options address particular consequences of mistakes, with the documented scope of each option defining the evidence they provide.
Expressing bit rotation directly
There is another useful C tooling change in this release. Clang adds rotation builtins for common integer widths. The Clang 8 language extensions manual specifies left and right rotation operations for widths of 8, 16, 32 and 64 bits. Both arguments and the result have the width named by the builtin. The rotation amount is unsigned and reduced modulo that width.
For a 32-bit value, the operation can be written without spelling out a pair of shifts and their combination. This is a compiler extension, so its use belongs in code that deliberately depends on Clang's documented interface.
#include <stdint.h>
uint32_t rotate_word(uint32_t value, uint32_t amount)
{
return __builtin_rotateleft32(value, amount);
}
A count of 32 therefore requests the same rotation as a count of zero. The modulo rule is part of the builtin's contract, so the caller does not need a separate zero-count branch to express that case. I prefer an explicit operation like this when the compiler dependency is acceptable. It makes the intended arithmetic visible while leaving the implementation to the compiler, and the versioned documentation states exactly how the count is interpreted.