Skip to content

Commit 9830cc0

Browse files
Implement MISRA-C++23 Preprocesser package rules 19-0-4, 19-1-1, and 19-2-1.
1 parent eedca61 commit 9830cc0

34 files changed

+724
-3
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
//** THIS FILE IS AUTOGENERATED, DO NOT MODIFY DIRECTLY. **/
2+
import cpp
3+
import RuleMetadata
4+
import codingstandards.cpp.exclusions.RuleMetadata
5+
6+
newtype PreprocessorQuery =
7+
TUndefOfMacroNotDefinedInFileQuery() or
8+
TInvalidTokenInDefinedOperatorQuery() or
9+
TDefinedOperatorExpandedInIfDirectiveQuery() or
10+
TNoValidIfdefGuardInHeaderQuery()
11+
12+
predicate isPreprocessorQueryMetadata(Query query, string queryId, string ruleId, string category) {
13+
query =
14+
// `Query` instance for the `undefOfMacroNotDefinedInFile` query
15+
PreprocessorPackage::undefOfMacroNotDefinedInFileQuery() and
16+
queryId =
17+
// `@id` for the `undefOfMacroNotDefinedInFile` query
18+
"cpp/misra/undef-of-macro-not-defined-in-file" and
19+
ruleId = "RULE-19-0-4" and
20+
category = "advisory"
21+
or
22+
query =
23+
// `Query` instance for the `invalidTokenInDefinedOperator` query
24+
PreprocessorPackage::invalidTokenInDefinedOperatorQuery() and
25+
queryId =
26+
// `@id` for the `invalidTokenInDefinedOperator` query
27+
"cpp/misra/invalid-token-in-defined-operator" and
28+
ruleId = "RULE-19-1-1" and
29+
category = "required"
30+
or
31+
query =
32+
// `Query` instance for the `definedOperatorExpandedInIfDirective` query
33+
PreprocessorPackage::definedOperatorExpandedInIfDirectiveQuery() and
34+
queryId =
35+
// `@id` for the `definedOperatorExpandedInIfDirective` query
36+
"cpp/misra/defined-operator-expanded-in-if-directive" and
37+
ruleId = "RULE-19-1-1" and
38+
category = "required"
39+
or
40+
query =
41+
// `Query` instance for the `noValidIfdefGuardInHeader` query
42+
PreprocessorPackage::noValidIfdefGuardInHeaderQuery() and
43+
queryId =
44+
// `@id` for the `noValidIfdefGuardInHeader` query
45+
"cpp/misra/no-valid-ifdef-guard-in-header" and
46+
ruleId = "RULE-19-2-1" and
47+
category = "required"
48+
}
49+
50+
module PreprocessorPackage {
51+
Query undefOfMacroNotDefinedInFileQuery() {
52+
//autogenerate `Query` type
53+
result =
54+
// `Query` type for `undefOfMacroNotDefinedInFile` query
55+
TQueryCPP(TPreprocessorPackageQuery(TUndefOfMacroNotDefinedInFileQuery()))
56+
}
57+
58+
Query invalidTokenInDefinedOperatorQuery() {
59+
//autogenerate `Query` type
60+
result =
61+
// `Query` type for `invalidTokenInDefinedOperator` query
62+
TQueryCPP(TPreprocessorPackageQuery(TInvalidTokenInDefinedOperatorQuery()))
63+
}
64+
65+
Query definedOperatorExpandedInIfDirectiveQuery() {
66+
//autogenerate `Query` type
67+
result =
68+
// `Query` type for `definedOperatorExpandedInIfDirective` query
69+
TQueryCPP(TPreprocessorPackageQuery(TDefinedOperatorExpandedInIfDirectiveQuery()))
70+
}
71+
72+
Query noValidIfdefGuardInHeaderQuery() {
73+
//autogenerate `Query` type
74+
result =
75+
// `Query` type for `noValidIfdefGuardInHeader` query
76+
TQueryCPP(TPreprocessorPackageQuery(TNoValidIfdefGuardInHeaderQuery()))
77+
}
78+
}

cpp/common/src/codingstandards/cpp/exclusions/cpp/RuleMetadata.qll

+3
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import Operators
4040
import OrderOfEvaluation
4141
import OutOfBounds
4242
import Pointers
43+
import Preprocessor
4344
import Representation
4445
import Scope
4546
import SideEffects1
@@ -94,6 +95,7 @@ newtype TCPPQuery =
9495
TOrderOfEvaluationPackageQuery(OrderOfEvaluationQuery q) or
9596
TOutOfBoundsPackageQuery(OutOfBoundsQuery q) or
9697
TPointersPackageQuery(PointersQuery q) or
98+
TPreprocessorPackageQuery(PreprocessorQuery q) or
9799
TRepresentationPackageQuery(RepresentationQuery q) or
98100
TScopePackageQuery(ScopeQuery q) or
99101
TSideEffects1PackageQuery(SideEffects1Query q) or
@@ -148,6 +150,7 @@ predicate isQueryMetadata(Query query, string queryId, string ruleId, string cat
148150
isOrderOfEvaluationQueryMetadata(query, queryId, ruleId, category) or
149151
isOutOfBoundsQueryMetadata(query, queryId, ruleId, category) or
150152
isPointersQueryMetadata(query, queryId, ruleId, category) or
153+
isPreprocessorQueryMetadata(query, queryId, ruleId, category) or
151154
isRepresentationQueryMetadata(query, queryId, ruleId, category) or
152155
isScopeQueryMetadata(query, queryId, ruleId, category) or
153156
isSideEffects1QueryMetadata(query, queryId, ruleId, category) or
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
private import codeql.util.DenseRank
2+
3+
/**
4+
* Describes how to construct a condensed list from sparse but orderable data, and how that data
5+
* should be connected, with one such list per specified division.
6+
*/
7+
signature module CondensedListSig {
8+
/**
9+
* The division specifies which items are connected into lists, with one list per division.
10+
*
11+
* For instance, if connecting variables defined in a file, the division will be the file.
12+
*/
13+
class Division;
14+
15+
/**
16+
* The class of the items to be condensed into lists.
17+
*
18+
* For instance, when connecting variables defined in a file, the items are the variables.
19+
*/
20+
class Item {
21+
string toString();
22+
}
23+
24+
/**
25+
* The index specifies the order of the items in the condensed list, and may be sparse (have
26+
* gaps).
27+
*
28+
* For instance, if connecting variables defined in a file, the index will be the line number of
29+
* the variable in the file.
30+
*
31+
* The sparse index (which may have gaps) is used to determine the ordering of the items in the
32+
* condensed list. Once the condensed list is created, the items in the list will automatically be
33+
* assigned a dense index (which has no gaps).
34+
*
35+
* There must be no duplicate indices for the same division for correctness.
36+
*/
37+
int getSparseIndex(Division d, Item l);
38+
}
39+
40+
/**
41+
* A module to take orderable data (which may not be continuous) and condense it into one or more
42+
* dense lists, with one such list per specified division.
43+
*
44+
* To instantiate this module, you need to provide a `CondensedListSig` module that
45+
* specifies the spare index and division of the items to be connected.
46+
*
47+
* For instance, to create a condensed list of variables defined in every file, you can
48+
* create a `CondensedListSig` module that specifies the file as the division and
49+
* the line number as the sparse index.
50+
*
51+
* ```ql
52+
* module ConfigFileListConfig {
53+
* class Division = File;
54+
* class Item = Variable;
55+
* int getSparseIndex(File file, Variable var) {
56+
* file = var.getLocation().getFile() and
57+
* var.getLocation().getStartLine()
58+
* }
59+
* }
60+
*
61+
* import Condense<ConfigFileListConfig>
62+
*
63+
* from Condense::ListEntry l
64+
* select l, l.getItem(), l.getDenseIndex(), l.getNext(), l.getPrev(),
65+
* ```
66+
*/
67+
module Condense<CondensedListSig Config> {
68+
newtype TList =
69+
THead(Config::Item l, Config::Division t) { denseRank(t, l) = 1 } or
70+
TCons(ListEntry prev, Config::Item l) { prev.getDenseIndex() = denseRank(prev.getDivision(), l) - 1 }
71+
72+
private module DenseRankConfig implements DenseRankInputSig2 {
73+
class Ranked = Config::Item;
74+
75+
class C = Config::Division;
76+
77+
predicate getRank = Config::getSparseIndex/2;
78+
}
79+
80+
private import DenseRank2<DenseRankConfig>
81+
82+
class ListEntry extends TList {
83+
Config::Division getDivision() {
84+
this = THead(_, result)
85+
or
86+
exists(ListEntry prev | this = TCons(prev, _) and result = prev.getDivision())
87+
}
88+
89+
string toString() {
90+
result = getItem().toString() + " [index " + getDenseIndex() + "]"
91+
}
92+
93+
Config::Item getItem() {
94+
this = THead(result, _)
95+
or
96+
this = TCons(_, result)
97+
}
98+
99+
int getDenseIndex() {
100+
result = denseRank(getDivision(), getItem())
101+
}
102+
103+
ListEntry getPrev() { this = TCons(result, _) }
104+
105+
ListEntry getNext() { result.getPrev() = this }
106+
}
107+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
bindingset[this]
2+
signature class ItemSig {
3+
bindingset[this]
4+
string toString();
5+
}
6+
7+
module Pair<ItemSig A, ItemSig B> {
8+
signature predicate pred(A a, B b);
9+
10+
module Where<pred/2 ctor> {
11+
private newtype TAll = TSome(A a, B b) {
12+
ctor(a, b)
13+
}
14+
15+
class Pair extends TAll {
16+
A getFirst() {
17+
this = TSome(result, _)
18+
}
19+
20+
B getSecond() {
21+
this = TSome(_, result)
22+
}
23+
24+
string toString() {
25+
result = getFirst().toString() + ", " + getSecond().toString()
26+
}
27+
}
28+
}
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/**
2+
* @id cpp/misra/undef-of-macro-not-defined-in-file
3+
* @name RULE-19-0-4: #undef should only be used for macros defined previously in the same file
4+
* @description Using #undef to undefine a macro that is not defined in the same file can lead to
5+
* confusion.
6+
* @kind problem
7+
* @precision very-high
8+
* @problem.severity warning
9+
* @tags external/misra/id/rule-19-0-4
10+
* scope/single-translation-unit
11+
* readability
12+
* maintanability
13+
* external/misra/enforcement/decidable
14+
* external/misra/obligation/advisory
15+
*/
16+
17+
import cpp
18+
import codingstandards.cpp.misra
19+
import codingstandards.cpp.util.CondensedList
20+
import codingstandards.cpp.util.Pair
21+
22+
class DefOrUndef extends PreprocessorDirective {
23+
string name;
24+
25+
DefOrUndef() {
26+
name = this.(PreprocessorUndef).getName() or
27+
name = this.(Macro).getName()
28+
}
29+
30+
string getName() { result = name }
31+
}
32+
33+
predicate relevantNameAndFile(string name, File file) {
34+
exists(DefOrUndef m |
35+
m.getName() = name and
36+
m.getFile() = file
37+
)
38+
}
39+
40+
class StringFilePair = Pair<string, File>::Where<relevantNameAndFile/2>::Pair;
41+
42+
module DefUndefListConfig implements CondensedListSig {
43+
class Division = StringFilePair;
44+
45+
class Item = DefOrUndef;
46+
47+
int getSparseIndex(StringFilePair division, DefOrUndef directive) {
48+
directive.getName() = division.getFirst() and
49+
directive.getFile() = division.getSecond() and
50+
result = directive.getLocation().getStartLine()
51+
}
52+
}
53+
54+
class ListEntry = Condense<DefUndefListConfig>::ListEntry;
55+
56+
from PreprocessorUndef undef, ListEntry defUndefListEntry
57+
where
58+
not isExcluded(undef, PreprocessorPackage::undefOfMacroNotDefinedInFileQuery()) and
59+
// There exists a def or undef for a given name and file, and it is an #undef
60+
undef = defUndefListEntry.getItem() and
61+
// Exclude cases where the previous def or undef with the same name in the same file is a #define
62+
not exists(ListEntry prev |
63+
prev = defUndefListEntry.getPrev() and
64+
prev.getItem() instanceof Macro
65+
)
66+
select undef, "Undef of name '" + undef.getName() + "' not defined in the same file."
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/**
2+
* @id cpp/misra/defined-operator-expanded-in-if-directive
3+
* @name RULE-19-1-1: The defined preprocessor operator shall be used appropriately
4+
* @description Macro expansions that produce the token 'defined' inside of an if directive result
5+
* in undefined behavior.
6+
* @kind problem
7+
* @precision very-high
8+
* @problem.severity error
9+
* @tags external/misra/id/rule-19-1-1
10+
* scope/single-translation-unit
11+
* correctness
12+
* maintainability
13+
* external/misra/enforcement/decidable
14+
* external/misra/obligation/required
15+
*/
16+
17+
import cpp
18+
import codingstandards.cpp.misra
19+
20+
from PreprocessorIf ifDirective, MacroInvocation mi
21+
where
22+
not isExcluded(ifDirective, PreprocessorPackage::macroDefinitionContainsDefinedOperatorQuery()) and
23+
ifDirective.getLocation().subsumes(mi.getLocation()) and
24+
mi.getMacro().getBody().regexpMatch(".*defined.*")
25+
select ifDirective,
26+
"If directive contains macro expansion including the token 'defined' from macro $@, which results in undefined behavior.",
27+
mi.getMacro(), mi.getMacroName()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/**
2+
* @id cpp/misra/invalid-token-in-defined-operator
3+
* @name RULE-19-1-1: The defined preprocessor operator shall be used appropriately
4+
* @description Using the defined operator without an immediately following optionally parenthesized
5+
* identifier results in undefined behavior.
6+
* @kind problem
7+
* @precision very-high
8+
* @problem.severity error
9+
* @tags external/misra/id/rule-19-1-1
10+
* scope/single-translation-unit
11+
* correctness
12+
* maintainability
13+
* external/misra/enforcement/decidable
14+
* external/misra/obligation/required
15+
*/
16+
17+
import cpp
18+
import codingstandards.cpp.misra
19+
20+
string idRegex() {
21+
result = "[a-zA-Z_]([a-zA-Z_0-9]*)"
22+
}
23+
24+
bindingset[body]
25+
predicate hasInvalidDefinedOperator(string body) {
26+
body.regexpMatch(
27+
// Contains text "defined" at a word break
28+
".*\\bdefined" +
29+
// Negative zero width lookahead:
30+
"(?!(" +
31+
// (group) optional whitespace followed by a valid identifier
32+
"(\\s*" + idRegex() + ")" +
33+
// or
34+
"|" +
35+
// (group) optional whitespace followed by parenthesis and valid identifier
36+
"(\\s*\\(\\s*" + idRegex() + "\\s*\\))" +
37+
// End negative zero width lookahead, match remaining text
38+
")).*")
39+
}
40+
41+
from PreprocessorIf ifDirective
42+
where
43+
not isExcluded(ifDirective, PreprocessorPackage::invalidTokenInDefinedOperatorQuery()) and
44+
hasInvalidDefinedOperator(ifDirective.getHead())
45+
select ifDirective, "Invalid use of defined operator in if directive."

0 commit comments

Comments
 (0)