Skip to content

Commit 5d247be

Browse files
authored
Merge pull request #769 from github/lcartey/rule-5-4-conditional-inclusion
`RULE-5-4`: Exclude results which do not occur in the same compilation, improve alert message
2 parents bde048b + 57dd748 commit 5d247be

File tree

12 files changed

+243
-9
lines changed

12 files changed

+243
-9
lines changed

c/misra/src/rules/RULE-5-4/MacroIdentifiersNotDistinct.ql

+84-8
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,56 @@
1515

1616
import cpp
1717
import codingstandards.c.misra
18+
import codingstandards.cpp.Macro
19+
import codingstandards.cpp.Includes
20+
import codingstandards.cpp.PreprocessorDirective
1821

19-
from Macro m, Macro m2
22+
/**
23+
* Gets a top level element that this macro is expanded to, e.g. an element which does not also have
24+
* an enclosing element in the macro.
25+
*/
26+
Element getATopLevelElement(MacroInvocation mi) {
27+
result = mi.getAnExpandedElement() and
28+
not result.getEnclosingElement() = mi.getAnExpandedElement() and
29+
not result instanceof Conversion
30+
}
31+
32+
/**
33+
* Gets a link target that this macro is expanded in.
34+
*/
35+
LinkTarget getALinkTarget(Macro m) {
36+
exists(MacroInvocation mi, Element e |
37+
mi = m.getAnInvocation() and
38+
e = getATopLevelElement(mi)
39+
|
40+
result = e.(Expr).getEnclosingFunction().getALinkTarget()
41+
or
42+
result = e.(Stmt).getEnclosingFunction().getALinkTarget()
43+
or
44+
exists(GlobalOrNamespaceVariable g |
45+
result = g.getALinkTarget() and
46+
g = e.(Expr).getEnclosingDeclaration()
47+
)
48+
)
49+
}
50+
51+
/**
52+
* Holds if the m1 and m2 are unconditionally included from a common file.
53+
*
54+
* Extracted out for performance reasons - otherwise the call to determine the file path for the
55+
* message was specializing the calls to `getAnUnconditionallyIncludedFile*(..)` and causing
56+
* slow performance.
57+
*/
58+
bindingset[m1, m2]
59+
pragma[inline_late]
60+
private predicate isIncludedUnconditionallyFromCommonFile(Macro m1, Macro m2) {
61+
exists(File f |
62+
getAnUnconditionallyIncludedFile*(f) = m1.getFile() and
63+
getAnUnconditionallyIncludedFile*(f) = m2.getFile()
64+
)
65+
}
66+
67+
from Macro m, Macro m2, string message
2068
where
2169
not isExcluded(m, Declarations1Package::macroIdentifiersNotDistinctQuery()) and
2270
not m = m2 and
@@ -25,12 +73,40 @@ where
2573
//C90 states the first 31 characters of macro identifiers are significant and is not currently considered by this rule
2674
//ie an identifier differing on the 32nd character would be indistinct for C90 but distinct for C99
2775
//and is currently not reported by this rule
28-
if m.getName().length() >= 64
29-
then m.getName().prefix(63) = m2.getName().prefix(63)
30-
else m.getName() = m2.getName()
76+
if m.getName().length() >= 64 and not m.getName() = m2.getName()
77+
then (
78+
m.getName().prefix(63) = m2.getName().prefix(63) and
79+
message =
80+
"Macro identifer " + m.getName() + " is nondistinct in first 63 characters, compared to $@."
81+
) else (
82+
m.getName() = m2.getName() and
83+
message =
84+
"Definition of macro " + m.getName() +
85+
" is not distinct from alternative definition of $@ in " +
86+
m2.getLocation().getFile().getRelativePath() + "."
87+
)
3188
) and
3289
//reduce double report since both macros are in alert, arbitrary ordering
33-
m.getLocation().getStartLine() >= m2.getLocation().getStartLine()
34-
select m,
35-
"Macro identifer " + m.getName() + " is nondistinct in first 63 characters, compared to $@.", m2,
36-
m2.getName()
90+
m.getLocation().getStartLine() >= m2.getLocation().getStartLine() and
91+
// Not within an #ifndef MACRO_NAME
92+
not exists(PreprocessorIfndef ifBranch |
93+
m.getAGuard() = ifBranch or
94+
m2.getAGuard() = ifBranch
95+
|
96+
ifBranch.getHead() = m.getName()
97+
) and
98+
// Must be included unconditionally from the same file, otherwise m1 may not be defined
99+
// when m2 is defined
100+
isIncludedUnconditionallyFromCommonFile(m, m2) and
101+
// Macros can't be mutually exclusive
102+
not mutuallyExclusiveBranchDirectiveMacros(m, m2) and
103+
not mutuallyExclusiveBranchDirectiveMacros(m2, m) and
104+
// If at least one invocation exists for at least one of the macros, then they must share a link
105+
// target - i.e. must both be expanded in the same context
106+
(
107+
(exists(m.getAnInvocation()) and exists(m2.getAnInvocation()))
108+
implies
109+
// Must share a link target - e.g. must both be expanded in the same context
110+
getALinkTarget(m) = getALinkTarget(m2)
111+
)
112+
select m, message, m2, m2.getName()
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
1+
| header3.h:7:1:7:24 | #define MULTIPLE_INCLUDE | Definition of macro MULTIPLE_INCLUDE is not distinct from alternative definition of $@ in rules/RULE-5-4/header4.h. | header4.h:1:1:1:24 | #define MULTIPLE_INCLUDE | MULTIPLE_INCLUDE |
2+
| header3.h:14:1:14:21 | #define NOT_PROTECTED | Definition of macro NOT_PROTECTED is not distinct from alternative definition of $@ in rules/RULE-5-4/header4.h. | header4.h:12:1:12:23 | #define NOT_PROTECTED 1 | NOT_PROTECTED |
13
| test.c:2:1:2:72 | #define iltiqzxgfqsgigwfuyntzghvzltueatcxqnqofnnvjyszmcsylyohvqaosjbqyyB | Macro identifer iltiqzxgfqsgigwfuyntzghvzltueatcxqnqofnnvjyszmcsylyohvqaosjbqyyB is nondistinct in first 63 characters, compared to $@. | test.c:1:1:1:72 | #define iltiqzxgfqsgigwfuyntzghvzltueatcxqnqofnnvjyszmcsylyohvqaosjbqyyA | iltiqzxgfqsgigwfuyntzghvzltueatcxqnqofnnvjyszmcsylyohvqaosjbqyyA |
2-
| test.c:8:1:8:31 | #define FUNCTION_MACRO(X) X + 1 | Macro identifer FUNCTION_MACRO is nondistinct in first 63 characters, compared to $@. | test.c:7:1:7:57 | #define FUNCTION_MACRO(FUNCTION_MACRO) FUNCTION_MACRO + 1 | FUNCTION_MACRO |
4+
| test.c:8:1:8:31 | #define FUNCTION_MACRO(X) X + 1 | Definition of macro FUNCTION_MACRO is not distinct from alternative definition of $@ in rules/RULE-5-4/test.c. | test.c:7:1:7:57 | #define FUNCTION_MACRO(FUNCTION_MACRO) FUNCTION_MACRO + 1 | FUNCTION_MACRO |
+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#ifdef FOO
2+
#include "header1.h"
3+
#else
4+
#include "header2.h"
5+
#endif
6+
7+
#ifdef FOO
8+
#define A_MACRO 1 // COMPLIANT
9+
#else
10+
#define A_MACRO 2 // COMPLIANT
11+
#endif

c/misra/test/rules/RULE-5-4/header1.h

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
#define REPEATED 11 // COMPLIANT

c/misra/test/rules/RULE-5-4/header2.h

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
#define REPEATED 1 // COMPLIANT

c/misra/test/rules/RULE-5-4/header3.h

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#ifndef HEADER3_H
2+
#define HEADER3_H
3+
4+
// We should ignore the header guards in this file
5+
6+
// This is defined unconditionally by both header3.h and header4.h
7+
#define MULTIPLE_INCLUDE // NON_COMPLIANT
8+
9+
// This is redefined in header3.h, but only if it isn't already defined
10+
#define PROTECTED // COMPLIANT
11+
12+
// This is redefined in header3.h, but is conditional on some other condition,
13+
// so this is redefined
14+
#define NOT_PROTECTED // NON_COMPLIANT
15+
16+
#endif

c/misra/test/rules/RULE-5-4/header4.h

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#define MULTIPLE_INCLUDE // NON_COMPLIANT
2+
3+
// This case is triggered from root2.c
4+
// because PROTECTED isn't defined in
5+
// that case
6+
#ifndef PROTECTED
7+
#define PROTECTED // COMPLIANT - checked by guard
8+
#endif
9+
10+
// Always enabled, so conflicts in root1.c case
11+
#ifdef MULTIPLE_INCLUDE
12+
#define NOT_PROTECTED 1 // NON_COMPLIANT
13+
#endif

c/misra/test/rules/RULE-5-4/root1.c

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#define FOO 1
2+
#include "conditional.h"
3+
4+
// Both headers define MULTIPLE_INCLUDE
5+
#include "header3.h"
6+
#include "header4.h"

c/misra/test/rules/RULE-5-4/root2.c

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#include "conditional.h"
2+
3+
#include "header4.h"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
- `RULE-5-4` - `MacroIdentifiersNotDistinct.ql`:
2+
- Exclude false positives related to conditional compilation, where a macro may be defined twice, but not within the same compilation.
3+
- Improve alert message in the case the 63 char limit is not relevant by using the form "Definition of macro `<MACRO_NAME>` is not distinct from alternative definition of `<MACRO_NAME>` in `<relative_file_path>`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/** A library which supports analysis of includes. */
2+
3+
import cpp
4+
import codingstandards.cpp.PreprocessorDirective
5+
import semmle.code.cpp.headers.MultipleInclusion
6+
7+
pragma[noinline]
8+
private predicate hasIncludeLocation(Include include, string filepath, int startline) {
9+
include.getLocation().hasLocationInfo(filepath, startline, _, _, _)
10+
}
11+
12+
/**
13+
* Holds if `include` is included conditionally based on the branch directive `b1`.
14+
*/
15+
pragma[noinline]
16+
predicate isConditionallyIncluded(PreprocessorBranchDirective bd, Include include) {
17+
not bd = any(CorrectIncludeGuard c).getIfndef() and
18+
not bd.getHead().regexpMatch(".*_H(_.*)?") and
19+
exists(string filepath, int startline, int endline, int includeline |
20+
isBranchDirectiveRange(bd, filepath, startline, endline) and
21+
hasIncludeLocation(include, filepath, includeline) and
22+
startline < includeline and
23+
endline > includeline
24+
)
25+
}
26+
27+
/**
28+
* Gets a file which is directly included from `fromFile` unconditionally.
29+
*/
30+
File getAnUnconditionallyIncludedFile(File fromFile) {
31+
// Find an include which isn't conditional
32+
exists(Include i |
33+
i.getFile() = fromFile and
34+
not isConditionallyIncluded(_, i) and
35+
result = i.getIncludedFile()
36+
)
37+
}

cpp/common/src/codingstandards/cpp/PreprocessorDirective.qll

+65
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,68 @@ class PreprocessorIfOrElif extends PreprocessorBranch {
4040
this instanceof PreprocessorElif
4141
}
4242
}
43+
44+
/**
45+
* Holds if the preprocessor directive `m` is located at `filepath` and `startline`.
46+
*/
47+
pragma[noinline]
48+
predicate hasPreprocessorLocation(PreprocessorDirective m, string filepath, int startline) {
49+
m.getLocation().hasLocationInfo(filepath, startline, _, _, _)
50+
}
51+
52+
/**
53+
* Holds if `first` and `second` are a pair of branch directives in the same file, such that they
54+
* share the same root if condition.
55+
*/
56+
pragma[noinline]
57+
private predicate isBranchDirectivePair(
58+
PreprocessorBranchDirective first, PreprocessorBranchDirective second, string filepath,
59+
int b1StartLocation, int b2StartLocation
60+
) {
61+
first.getIf() = second.getIf() and
62+
not first = second and
63+
hasPreprocessorLocation(first, filepath, b1StartLocation) and
64+
hasPreprocessorLocation(second, filepath, b2StartLocation) and
65+
b1StartLocation < b2StartLocation
66+
}
67+
68+
/**
69+
* Holds if `bd` is a branch directive in the range `filepath`, `startline`, `endline`.
70+
*/
71+
pragma[noinline]
72+
predicate isBranchDirectiveRange(
73+
PreprocessorBranchDirective bd, string filepath, int startline, int endline
74+
) {
75+
hasPreprocessorLocation(bd, filepath, startline) and
76+
exists(PreprocessorBranchDirective next |
77+
next = bd.getNext() and
78+
// Avoid referencing filepath here, otherwise the optimiser will try to join
79+
// on it
80+
hasPreprocessorLocation(next, _, endline)
81+
)
82+
}
83+
84+
/**
85+
* Holds if the macro `m` is defined within the branch directive `bd`.
86+
*/
87+
pragma[noinline]
88+
predicate isMacroDefinedWithinBranch(PreprocessorBranchDirective bd, Macro m) {
89+
exists(string filepath, int startline, int endline, int macroline |
90+
isBranchDirectiveRange(bd, filepath, startline, endline) and
91+
hasPreprocessorLocation(m, filepath, macroline) and
92+
startline < macroline and
93+
endline > macroline
94+
)
95+
}
96+
97+
/**
98+
* Holds if the pair of macros are "conditional" i.e. only one of the macros is followed in any
99+
* particular compilation of the containing file.
100+
*/
101+
predicate mutuallyExclusiveBranchDirectiveMacros(Macro firstMacro, Macro secondMacro) {
102+
exists(PreprocessorBranchDirective b1, PreprocessorBranchDirective b2 |
103+
isBranchDirectivePair(b1, b2, _, _, _) and
104+
isMacroDefinedWithinBranch(b1, firstMacro) and
105+
isMacroDefinedWithinBranch(b2, secondMacro)
106+
)
107+
}

0 commit comments

Comments
 (0)