Expand description
Which arm of a preprocessor conditional a unit sits in.
C and C++ sources are parsed unexpanded, so both arms of an #if are in
the IR at once. That is deliberate — the mode resolves no build conditions,
and dropping the arm a default configuration would not take would silently
hide code. It has a consequence for clone detection: the two arms of
#ifdef _WIN32
void sleep_ms(int ms) { Sleep(ms); }
#else
void sleep_ms(int ms) { usleep(ms * 1000); }
#endifmeasure as near-identical, and a report that calls them duplicates is telling the reader to remove one. They cannot: exactly one of them exists in any build, and which one is a build condition this mode does not resolve. Reporting the pair would also put two build variants in one finding, which the analysis does not do anywhere else.
So the relation is recorded here and the pair is dropped before verification, exactly as a unit nested inside another is: not because the finding would rank badly, but because it is not a statement about any one program.
§What this does not claim
Only syntactic exclusion is recognised: two units under arms of the same
conditional. Two units guarded by separate #ifs that happen to be
mutually exclusive — #ifdef A here and #ifndef A there — are not
related by this, because relating them means evaluating the conditions, and
the conditions are what this mode does not have.
§Only as good as the parse
The arms are read off the tree, so a conditional the parser could not
follow has no trustworthy arms. That is not hypothetical: a C++ header
parsed by the C grammar — which happens whenever a project puts C++ in .h
and the header policy says C — recovers into a shape where one #if
swallows the rest of the file and its #else holds everything after it.
Measured on one such header, that turned ten genuine arm pairs into eight
hundred.
Dropping a pair hides a finding, so the mistake is not symmetric: a missed exclusion costs a noisy line in a report, an invented one costs a clone nobody will ever see. A conditional is therefore only believed when the parser stumbled nowhere inside it; one that encloses an error region still nests, but relates none of the units under it.
The judgement is per conditional rather than per file because error
recovery is not local to what broke. A single unparsable construct puts an
error region in the file, and a header whose include guard encloses
everything gets one spanning the whole of it, which says nothing about the
#if twenty lines further down. Measured across three C++ projects,
believing a whole file only when it is error-free left 69% to 77% of the
arms the parser had in fact read cleanly unused.
Structs§
- ArmPath
- The conditionals enclosing a unit, outermost first.
- ArmTracker
- Tracks the lexical arm active at each token in a C-family source file.
Enums§
- Static
Condition - A literal condition that lexical preprocessing can establish without expanding macros or evaluating an expression.