Skip to content

Commit fde0df0

Browse files
authored
Merge pull request #29 from knewbury01/knewbury01/Preprocessor5
Package Preprocessor 5
2 parents 6135896 + fb74797 commit fde0df0

File tree

33 files changed

+652
-60
lines changed

33 files changed

+652
-60
lines changed

.vscode/tasks.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@
224224
"Preprocessor2",
225225
"Preprocessor3",
226226
"Preprocessor4",
227+
"Preprocessor5",
227228
"IntegerConversion",
228229
"Expressions",
229230
"DeadCode",
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# MSC38-C: Do not treat a predefined identifier as an object if it might only be implemented as a macro
2+
3+
This query implements the CERT-C rule MSC38-C:
4+
5+
> Do not treat a predefined identifier as an object if it might only be implemented as a macro
6+
7+
8+
## Description
9+
10+
The C Standard, 7.1.4 paragraph 1, \[[ISO/IEC 9899:2011](https://wiki.sei.cmu.edu/confluence/display/c/AA.+Bibliography#AA.Bibliography-ISO%2FIEC9899-2011)\] states
11+
12+
> Any function declared in a header may be additionally implemented as a function-like macro defined in the header, so if a library function is declared explicitly when its header is included, one of the techniques shown below can be used to ensure the declaration is not affected by such a macro. Any macro definition of a function can be suppressed locally by enclosing the name of the function in parentheses, because the name is then not followed by the left parenthesis that indicates expansion of a macro function name. For the same syntactic reason, it is permitted to take the address of a library function even if it is also defined as a macro.<sup>185</sup>
13+
14+
15+
185. This means that an implementation shall provide an actual function for each library function, even if it also provides a macro for that function.
16+
17+
However, the C Standard enumerates specific exceptions in which the behavior of accessing an object or function expanded to be a standard library macro definition is [undefined](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-undefinedbehavior). The macros are `assert`, `errno`, `math_errhandling`, `setjmp`, `va_arg`, `va_copy`, `va_end`, and `va_start`. These cases are described by [undefined behaviors](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-undefinedbehavior) [110](https://wiki.sei.cmu.edu/confluence/display/c/CC.+Undefined+Behavior#CC.UndefinedBehavior-ub_110), [114](https://wiki.sei.cmu.edu/confluence/display/c/CC.+Undefined+Behavior#CC.UndefinedBehavior-ub_114), [122](https://wiki.sei.cmu.edu/confluence/display/c/CC.+Undefined+Behavior#CC.UndefinedBehavior-ub_122), [124](https://wiki.sei.cmu.edu/confluence/display/c/CC.+Undefined+Behavior#CC.UndefinedBehavior-ub_124), and [138](https://wiki.sei.cmu.edu/confluence/display/c/CC.+Undefined+Behavior#CC.UndefinedBehavior-ub_138). Programmers must not suppress these macros to access the underlying object or function.
18+
19+
## Noncompliant Code Example (assert)
20+
21+
In this noncompliant code example, the standard `assert()` macro is suppressed in an attempt to pass it as a function pointer to the `execute_handler()` function. Attempting to suppress the `assert()` macro is [undefined behavior](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-undefinedbehavior).
22+
23+
```cpp
24+
#include <assert.h>
25+
26+
typedef void (*handler_type)(int);
27+
28+
void execute_handler(handler_type handler, int value) {
29+
handler(value);
30+
}
31+
32+
void func(int e) {
33+
execute_handler(&(assert), e < 0);
34+
}
35+
```
36+
37+
## Compliant Solution (assert)
38+
39+
In this compliant solution, the `assert()` macro is wrapped in a helper function, removing the [undefined behavior](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-undefinedbehavior):
40+
41+
```cpp
42+
#include <assert.h>
43+
44+
typedef void (*handler_type)(int);
45+
46+
void execute_handler(handler_type handler, int value) {
47+
handler(value);
48+
}
49+
50+
static void assert_handler(int value) {
51+
assert(value);
52+
}
53+
54+
void func(int e) {
55+
execute_handler(&assert_handler, e < 0);
56+
}
57+
```
58+
59+
## Noncompliant Code Example (Redefining errno)
60+
61+
Legacy code is apt to include an incorrect declaration, such as the following in this noncompliant code example:
62+
63+
```cpp
64+
extern int errno;
65+
66+
```
67+
68+
## Compliant Solution (Declaring errno)
69+
70+
This compliant solution demonstrates the correct way to declare `errno` by including the header `<errno.h>`:
71+
72+
```cpp
73+
#include <errno.h>
74+
75+
```
76+
[C-conforming](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-conformingprogram) [implementations](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-implementation) are required to declare `errno` in `<errno.h>`, although some historic implementations failed to do so.
77+
78+
## Risk Assessment
79+
80+
Accessing objects or functions underlying the specific macros enumerated in this rule is [undefined behavior](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-undefinedbehavior).
81+
82+
<table> <tbody> <tr> <th> Rule </th> <th> Severity </th> <th> Likelihood </th> <th> Remediation Cost </th> <th> Priority </th> <th> Level </th> </tr> <tr> <td> MSC38-C </td> <td> Low </td> <td> Unlikely </td> <td> Medium </td> <td> <strong>P2</strong> </td> <td> <strong>L3</strong> </td> </tr> </tbody> </table>
83+
84+
85+
## Automated Detection
86+
87+
<table> <tbody> <tr> <th> Tool </th> <th> Version </th> <th> Checker </th> <th> Description </th> </tr> <tr> <td> <a> Astrée </a> </td> <td> 22.04 </td> <td> </td> <td> Supported, but no explicit checker </td> </tr> <tr> <td> <a> CodeSonar </a> </td> <td> 7.0p0 </td> <td> <strong>BADMACRO.STDARG_H</strong> </td> <td> Use of &lt;stdarg.h&gt; Feature </td> </tr> <tr> <td> <a> Helix QAC </a> </td> <td> 2022.2 </td> <td> <strong>C3437, C3475</strong> <strong>C++3127, C++5039</strong> </td> <td> </td> </tr> <tr> <td> <a> Parasoft C/C++test </a> </td> <td> 2022.1 </td> <td> <strong>CERT_C-MSC38-a</strong> </td> <td> A function-like macro shall not be invoked without all of its arguments </td> </tr> <tr> <td> <a> Polyspace Bug Finder </a> </td> <td> R2022a </td> <td> <a> CERT C: Rule MSC38-C </a> </td> <td> Checks for predefined macro used as an object (rule fully covered) </td> </tr> <tr> <td> <a> PRQA QA-C </a> </td> <td> 9.7 </td> <td> <strong>3437, 3475</strong> </td> <td> </td> </tr> <tr> <td> <a> RuleChecker </a> </td> <td> 22.04 </td> <td> </td> <td> Supported, but no explicit checker </td> </tr> </tbody> </table>
88+
89+
90+
## Related Vulnerabilities
91+
92+
Search for [vulnerabilities](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-vulnerability) resulting from the violation of this rule on the [CERT website](https://www.kb.cert.org/vulnotes/bymetric?searchview&query=FIELD+KEYWORDS+contains+MSC38-C).
93+
94+
## Related Guidelines
95+
96+
[Key here](https://wiki.sei.cmu.edu/confluence/display/c/How+this+Coding+Standard+is+Organized#HowthisCodingStandardisOrganized-RelatedGuidelines) (explains table format and definitions)
97+
98+
<table> <tbody> <tr> <th> Taxonomy </th> <th> Taxonomy item </th> <th> Relationship </th> </tr> <tr> <td> <a> CERT C </a> </td> <td> <a> DCL37-C. Do not declare or define a reserved identifier </a> </td> <td> Prior to 2018-01-12: CERT: Unspecified Relationship </td> </tr> </tbody> </table>
99+
100+
101+
## Bibliography
102+
103+
<table> <tbody> <tr> <td> <a> ISO/IEC 9899:2011 </a> </td> <td> 7.1.4, "Use of Library Functions" </td> </tr> </tbody> </table>
104+
105+
106+
## Implementation notes
107+
108+
This query reports locations corresponding to both redefinitions of those standard library macros as well as locations where the identifiers used for accesses.
109+
110+
## References
111+
112+
* CERT-C: [MSC38-C: Do not treat a predefined identifier as an object if it might only be implemented as a macro](https://wiki.sei.cmu.edu/confluence/display/c)
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/**
2+
* @id c/cert/do-not-treat-a-predefined-identifier-as-object
3+
* @name MSC38-C: Do not treat a predefined identifier as an object if it might only be implemented as a macro
4+
* @description Accessing an object or function that expands to one of a few specific standard
5+
* library macros is undefined behaviour.
6+
* @kind problem
7+
* @precision very-high
8+
* @problem.severity warning
9+
* @tags external/cert/id/msc38-c
10+
* correctness
11+
* readability
12+
* external/cert/obligation/rule
13+
*/
14+
15+
import cpp
16+
import codingstandards.c.cert
17+
18+
predicate hasRestrictedMacroName(string s) {
19+
s = "assert"
20+
or
21+
s = "errno"
22+
or
23+
s = "math_errhandling"
24+
or
25+
s = "setjmp"
26+
or
27+
s = "va_arg"
28+
or
29+
s = "va_copy"
30+
or
31+
s = "va_end"
32+
or
33+
s = "va_start"
34+
}
35+
36+
from Element m, string name
37+
where
38+
not isExcluded(m, Preprocessor5Package::doNotTreatAPredefinedIdentifierAsObjectQuery()) and
39+
(
40+
m.(Access).getTarget().hasName(name)
41+
or
42+
m.(Declaration).hasGlobalName(name)
43+
) and
44+
hasRestrictedMacroName(name)
45+
select m, "Supression of standard library macro " + name + "."
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# PRE32-C: Do not use preprocessor directives in invocations of function-like macros
2+
3+
This query implements the CERT-C rule PRE32-C:
4+
5+
> Do not use preprocessor directives in invocations of function-like macros
6+
7+
8+
## Description
9+
10+
The arguments to a macro must not include preprocessor directives, such as `#define`, `#ifdef`, and `#include`. Doing so results in [undefined behavior](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-undefinedbehavior), according to the C Standard, 6.10.3, paragraph 11 \[[ISO/IEC 9899:2011](https://wiki.sei.cmu.edu/confluence/display/c/AA.+Bibliography#AA.Bibliography-ISO-IEC9899-2011)\]:
11+
12+
> The sequence of preprocessing tokens bounded by the outside-most matching parentheses forms the list of arguments for the function-like macro. The individual arguments within the list are separated by comma preprocessing tokens, but comma preprocessing tokens between matching inner parentheses do not separate arguments. **If there are sequences of preprocessing tokens within the list of arguments that would otherwise act as preprocessing directives, the behavior is undefined.**
13+
14+
15+
See also [undefined behavior 93](https://wiki.sei.cmu.edu/confluence/display/c/CC.+Undefined+Behavior#CC.UndefinedBehavior-ub_93).
16+
17+
This rule also applies to the use of preprocessor directives in arguments to any function where it is unknown whether or not the function is implemented using a macro. This includes all standard library functions, such as `memcpy()`, `printf()`, and `assert()`, because any standard library function may be implemented as a macro. (C11, 7.1.4, paragraph 1).
18+
19+
## Noncompliant Code Example
20+
21+
In this noncompliant code example \[[GCC Bugs](http://gcc.gnu.org/bugs.html#nonbugs_c)\], the programmer uses preprocessor directives to specify platform-specific arguments to `memcpy()`. However, if `memcpy()` is implemented using a macro, the code results in undefined behavior.
22+
23+
```cpp
24+
#include <string.h>
25+
26+
void func(const char *src) {
27+
/* Validate the source string; calculate size */
28+
char *dest;
29+
/* malloc() destination string */
30+
memcpy(dest, src,
31+
#ifdef PLATFORM1
32+
12
33+
#else
34+
24
35+
#endif
36+
);
37+
/* ... */
38+
}
39+
40+
```
41+
42+
## Compliant Solution
43+
44+
In this compliant solution \[[GCC Bugs](http://gcc.gnu.org/bugs.html#nonbugs_c)\], the appropriate call to `memcpy()` is determined outside the function call:
45+
46+
```cpp
47+
#include <string.h>
48+
49+
void func(const char *src) {
50+
/* Validate the source string; calculate size */
51+
char *dest;
52+
/* malloc() destination string */
53+
#ifdef PLATFORM1
54+
memcpy(dest, src, 12);
55+
#else
56+
memcpy(dest, src, 24);
57+
#endif
58+
/* ... */
59+
}
60+
```
61+
62+
## Risk Assessment
63+
64+
Including preprocessor directives in macro arguments is undefined behavior.
65+
66+
<table> <tbody> <tr> <th> Rule </th> <th> Severity </th> <th> Likelihood </th> <th> Remediation Cost </th> <th> Priority </th> <th> Level </th> </tr> <tr> <td> PRE32-C </td> <td> Low </td> <td> Unlikely </td> <td> Medium </td> <td> <strong>P2</strong> </td> <td> <strong>L3</strong> </td> </tr> </tbody> </table>
67+
68+
69+
## Automated Detection
70+
71+
<table> <tbody> <tr> <th> Tool </th> <th> Version </th> <th> Checker </th> <th> Description </th> </tr> <tr> <td> <a> Astrée </a> </td> <td> 22.04 </td> <td> <strong>macro-argument-hash</strong> </td> <td> Fully checked </td> </tr> <tr> <td> <a> Axivion Bauhaus Suite </a> </td> <td> 7.2.0 </td> <td> <strong>CertC-PRE32</strong> </td> <td> Fully implemented </td> </tr> <tr> <td> <a> CodeSonar </a> </td> <td> 7.0p0 </td> <td> <strong>LANG.PREPROC.MACROARG</strong> </td> <td> Preprocessing directives in macro argument </td> </tr> <tr> <td> <a> ECLAIR </a> </td> <td> 1.2 </td> <td> <strong>CC2.PRE32</strong> </td> <td> Fully implemented </td> </tr> <tr> <td> <a> Helix QAC </a> </td> <td> 2022.2 </td> <td> <strong>C0853</strong> <strong>C++1072</strong> </td> <td> </td> </tr> <tr> <td> <a> Klocwork </a> </td> <td> 2022.2 </td> <td> <strong>MISRA.EXPANSION.DIRECTIVE</strong> </td> <td> </td> </tr> <tr> <td> <a> LDRA tool suite </a> </td> <td> 9.7.1 </td> <td> <strong>341 S</strong> </td> <td> Fully implemented </td> </tr> <tr> <td> <a> Parasoft C/C++test </a> </td> <td> 2022.1 </td> <td> <strong>CERT_C-PRE32-a</strong> </td> <td> Arguments to a function-like macro shall not contain tokens that look like preprocessing directives </td> </tr> <tr> <td> <a> PC-lint Plus </a> </td> <td> 1.4 </td> <td> <strong>436, 9501</strong> </td> <td> Fully supported </td> </tr> <tr> <td> <a> Polyspace Bug Finder </a> </td> <td> R2022a </td> <td> <a> CERT C: Rule PRE32-C </a> </td> <td> Checks for preprocessor directive in macro argument (rule fully covered) </td> </tr> <tr> <td> <a> PRQA QA-C </a> </td> <td> 9.7 </td> <td> <strong>0853</strong> </td> <td> </td> </tr> <tr> <td> <a> PRQA QA-C++ </a> </td> <td> 4.4 </td> <td> <strong>1072 </strong> </td> <td> </td> </tr> <tr> <td> <a> RuleChecker </a> </td> <td> 22.04 </td> <td> <strong>macro-argument-hash</strong> </td> <td> Fully checked </td> </tr> </tbody> </table>
72+
73+
74+
## Related Vulnerabilities
75+
76+
Search for [vulnerabilities](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-vulnerability) resulting from the violation of this rule on the [CERT website](https://www.kb.cert.org/vulnotes/bymetric?searchview&query=FIELD+KEYWORDS+contains+PRE32-C).
77+
78+
## Bibliography
79+
80+
<table> <tbody> <tr> <td> \[ <a> GCC Bugs </a> \] </td> <td> "Non-bugs" </td> </tr> <tr> <td> \[ <a> ISO/IEC 9899:2011 </a> \] </td> <td> 6.10.3, "Macro Replacement" </td> </tr> </tbody> </table>
81+
82+
83+
## Implementation notes
84+
85+
This query defines end of function call as the next node in the control flow graph.
86+
87+
## References
88+
89+
* CERT-C: [PRE32-C: Do not use preprocessor directives in invocations of function-like macros](https://wiki.sei.cmu.edu/confluence/display/c)
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/**
2+
* @id c/cert/macro-or-function-args-contain-hash-token
3+
* @name PRE32-C: Do not use preprocessor directives in invocations of function-like macros
4+
* @description Arguments to a function-like macros shall not contain tokens that look like
5+
* pre-processing directives or else behaviour after macro expansion is unpredictable.
6+
* This rule applies to functions as well in case they are implemented using macros.
7+
* @kind problem
8+
* @precision high
9+
* @problem.severity warning
10+
* @tags external/cert/id/pre32-c
11+
* correctness
12+
* readability
13+
* external/cert/obligation/rule
14+
*/
15+
16+
import cpp
17+
import codingstandards.c.cert
18+
import codingstandards.cpp.PreprocessorDirective
19+
20+
pragma[noinline]
21+
predicate isFunctionInvocationLocation(FunctionCall call, File f, int startline, int endline) {
22+
call.getLocation().hasLocationInfo(f.getAbsolutePath(), startline, _, _, _) and
23+
isFunctionSuccessorLocation(call.getASuccessor(), f, endline)
24+
}
25+
26+
pragma[noinline]
27+
predicate isFunctionSuccessorLocation(ControlFlowNode node, File f, int endline) {
28+
//for all function calls the closest location heurisitc we have for end line is the next node in cfg
29+
node.getLocation().hasLocationInfo(f.getAbsolutePath(), endline, _, _, _)
30+
}
31+
32+
PreprocessorDirective isLocatedInAFunctionInvocation(FunctionCall c) {
33+
exists(PreprocessorDirective p, File f, int startCall, int endCall |
34+
isFunctionInvocationLocation(c, f, startCall, endCall) and
35+
exists(int startLine, int endLine | isPreprocDirectiveLine(p, f, startLine, endLine) |
36+
startCall < startLine and
37+
startCall < endLine and
38+
endLine <= endCall and
39+
endLine <= endCall
40+
) and
41+
result = p
42+
)
43+
}
44+
45+
from PreprocessorDirective p, string msg
46+
where
47+
not isExcluded(p, Preprocessor5Package::macroOrFunctionArgsContainHashTokenQuery()) and
48+
exists(FunctionCall c |
49+
p = isLocatedInAFunctionInvocation(c) and
50+
//if this is actually a function in a macro ignore it because it will still be seen in the macro condition
51+
not c.isInMacroExpansion() and
52+
msg =
53+
"Invocation of function " + c.getTarget().getName() + " includes a token \"" + p +
54+
"\" that could be confused for an argument preprocessor directive."
55+
)
56+
or
57+
exists(MacroInvocation m |
58+
p = isLocatedInAMacroInvocation(m) and
59+
msg =
60+
"Invocation of macro " + m.getMacroName() + " includes a token \"" + p +
61+
"\" that could be confused for an argument preprocessor directive."
62+
)
63+
select p, msg
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
| assert.h:2:6:2:11 | assert | Supression of standard library macro assert. |
2+
| test.c:3:12:3:16 | errno | Supression of standard library macro errno. |
3+
| test.c:10:21:10:26 | assert | Supression of standard library macro assert. |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
rules/MSC38-C/DoNotTreatAPredefinedIdentifierAsObject.ql

c/cert/test/rules/MSC38-C/assert.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#undef assert
2+
void assert(int i); // NON_COMPLIANT
3+
4+
#define assert(x) (void)0

c/cert/test/rules/MSC38-C/test.c

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#include "assert.h"
2+
3+
extern int errno; // NON_COMPLIANT
4+
5+
typedef void (*handler_type)(int);
6+
7+
void execute_handler(handler_type handler, int value) { handler(value); }
8+
9+
void f(int e) {
10+
execute_handler(&(assert), e < 0); // NON_COMPLIANT
11+
}
12+
13+
static void assert_handler(int value) { assert(value); }
14+
15+
void f2(int e) {
16+
execute_handler(&assert_handler, e < 0); // COMPLIANT
17+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
| test.c:9:1:9:19 | #if NOTDEFINEDMACRO | Invocation of macro MACROFUNCTION includes a token "#if NOTDEFINEDMACRO" that could be confused for an argument preprocessor directive. |
2+
| test.c:11:1:11:5 | #else | Invocation of macro MACROFUNCTION includes a token "#else" that could be confused for an argument preprocessor directive. |
3+
| test.c:13:1:13:6 | #endif | Invocation of macro MACROFUNCTION includes a token "#endif" that could be confused for an argument preprocessor directive. |
4+
| test.c:20:1:20:16 | #ifdef SOMEMACRO | Invocation of function memcpy includes a token "#ifdef SOMEMACRO" that could be confused for an argument preprocessor directive. |
5+
| test.c:22:1:22:5 | #else | Invocation of function memcpy includes a token "#else" that could be confused for an argument preprocessor directive. |
6+
| test.c:24:1:24:6 | #endif | Invocation of function memcpy includes a token "#endif" that could be confused for an argument preprocessor directive. |
7+
| test.c:27:1:27:8 | #if TEST | Invocation of function memcpy includes a token "#if TEST" that could be confused for an argument preprocessor directive. |
8+
| test.c:28:1:28:6 | #endif | Invocation of function memcpy includes a token "#endif" that could be confused for an argument preprocessor directive. |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
rules/PRE32-C/MacroOrFunctionArgsContainHashToken.ql

c/cert/test/rules/PRE32-C/test.c

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
2+
#include <string.h>
3+
#define MACROFUNCTION(X) strlen(X)
4+
5+
void func(const char *src) {
6+
char *dest;
7+
8+
MACROFUNCTION(
9+
#if NOTDEFINEDMACRO // NON_COMPLIANT
10+
"longstringtest!test!"
11+
#else // NON_COMPLIANT
12+
"shortstring"
13+
#endif // NON_COMPLIANT
14+
);
15+
16+
MACROFUNCTION("alright"); // COMPLIANT
17+
memcpy(dest, src, 12); // COMPLIANT
18+
19+
memcpy(dest, src,
20+
#ifdef SOMEMACRO // NON_COMPLIANT
21+
12
22+
#else // NON_COMPLIANT
23+
24
24+
#endif // NON_COMPLIANT
25+
);
26+
27+
#if TEST // COMPLIANT[FALSE_POSITIVE]
28+
#endif // COMPLIANT[FALSE_POSITIVE]
29+
}

0 commit comments

Comments
 (0)