March 13, 2017·4 min read·engineeringccompilersdiagnostics

Clang 4 lets C declarations diagnose invalid calls

Clang 4 adds diagnose_if for conditional compile-time diagnostics. The attribute can explain an interface restriction at a call without adding a runtime check.

A C function declaration can describe argument types while leaving much of the actual interface contract in prose. An integer parameter might accept only positive values, yet its type permits zero and negative numbers. Clang 4 adds diagnose_if, an attribute that attaches a diagnostic to a condition involving a call. When the compiler can evaluate that condition, it can report a library-specific warning or error at the place that supplies the argument.

The LLVM release archive records the March 13 release of 4.0.0. Its Clang release notes identify diagnose_if as a major addition. For C interfaces, the interesting change is the ability to put a restricted argument domain beside the declaration that callers already include. This avoids requiring a reader to discover every restriction from an implementation body or separate manual.

I value C interfaces that keep their assumptions visible. The language gives a declaration limited ways to describe relationships among argument values, so a diagnostic extension can be useful even when it does not change which values the type system represents. The important question is exactly what the compiler promises to check.

A diagnostic condition belongs to a declaration

The versioned attribute reference documents GNU attribute syntax for diagnose_if. Its arguments are a condition, a diagnostic message and a severity string. The condition specifies when to emit the message, so a true condition identifies the problematic call.

A declaration can therefore reject an obvious invalid constant without altering the ordinary function type. This example describes an interface whose channel count must be positive. It uses Clang-specific syntax and is not an ISO C facility.

void configure_channels(int count)
    __attribute__((diagnose_if(count <= 0,
                              "channel count must be positive",
                              "error")));

void configure_known_values(void)
{
    configure_channels(2);
    configure_channels(0); /* compile-time error */
}

The second call makes the condition true with an argument the compiler can evaluate. Its message expresses the interface rule directly, so the diagnostic can be more specific than a generic type complaint. The first call does not meet the error condition. Both calls otherwise supply an integer to a function declared to take an integer.

Severity is a library design decision. The reference permits a warning or an error, allowing an interface to distinguish a discouraged call from one it intends to reject during compilation. A warning can explain a suspicious but supported operation. An error is appropriate only when the condition accurately captures a rule whose violation should prevent the build.

The compiler also provides __has_attribute(diagnose_if) for feature detection. Header authors can use that query when deciding whether to expose the annotation. Other compilers still need a declaration they understand, so using the extension in a shared header requires a compatibility policy. Omitting the annotation on another compiler also omits this diagnostic assistance.

Unknown values remain a runtime responsibility

The attribute reference states that an unevaluable condition produces no diagnostic. This is an essential part of its meaning. A function parameter received from elsewhere may have a value that is unknown when the compiler examines the call, even if the application can eventually supply an invalid number.

void configure_from_input(int count)
{
    configure_channels(count);
}

This wrapper does not establish that count is positive. The attribute does not insert a branch that checks it at runtime. An implementation that requires protection against invalid external input must still handle that input through its ordinary runtime logic.

That behavior separates a conditional diagnostic from a dynamic assertion. The former uses information available during compilation, while the latter executes as part of the program. They can express the same condition in different places, but one cannot substitute for the other merely because its message appears in a header.

It also explains why a successful compilation is a limited result. It means this attribute did not produce an error for the calls the compiler examined. Successful compilation does not prove that every invocation satisfies the interface restriction in every possible execution of the program. This limitation follows directly from the documented rule for conditions the compiler cannot evaluate.

Declarations can accumulate diagnostics

diagnose_if does not create distinct function overloads. Declarations that differ only in these attributes redeclare the same function, and their diagnostic conditions accumulate. The reference demonstrates multiple warnings attached to one function, each with its own message.

That gives a header author room to describe separate restrictions without combining every case into one expression and one diagnostic. An interface might distinguish an invalid argument from a discouraged compatibility mode. Separate messages can identify which condition applies, while the function retains its usual identity.

The reference compares this behavior with enable_if. For languages and extensions that perform overload resolution, diagnose_if does not participate in choosing the candidate. The diagnostic applies after selection. For a C reader, the practical lesson is that this attribute reports on a call to the declared function. It does not supply a runtime dispatcher or an alternate implementation for different argument values.

Tooling changes have their own scope

Clang 4 also adds -MJ to simplify producing JSON compilation database output from existing build systems. The release notes place that alongside -Og, an option intended for the debugging experience. In this release, -Og is exactly equivalent to -O1, so its name should not be read as a separate optimization algorithm already implemented in 4.0.

These changes concern different parts of working with C. Compilation database output helps tools obtain build commands, while diagnose_if lets an interface author supply semantic information for the compiler. Neither implies that all of the release notes describe ordinary C. The same document includes OpenCL changes and analyzer checks for C++ and Objective-C, whose applicability depends on the language being compiled.

I prefer the declaration-level addition because its benefit is inspectable. A reviewer can read the condition and message beside the function signature, then identify which invalid calls it can diagnose. The remaining runtime responsibility is equally explicit in the documented rule that unknown conditions produce no check. That makes the annotation useful for catching constant mistakes while preserving a clear account of what the implementation must still validate.