Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit a653a58

Browse filesBrowse files
Implement package preprocessor2
1 parent a31e047 commit a653a58
Copy full SHA for a653a58

17 files changed

+752
-1
lines changed

‎cpp/common/src/codingstandards/cpp/Macro.qll

Copy file name to clipboardExpand all lines: cpp/common/src/codingstandards/cpp/Macro.qll
+43Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,49 @@ class FunctionLikeMacro extends Macro {
1919
exists(this.getBody().regexpFind("\\#?\\b" + parameter + "\\b", _, result))
2020
)
2121
}
22+
23+
/**
24+
* Holds if the parameter is used in a way that may make it vulnerable to precedence issues.
25+
*
26+
* Typically, parameters are wrapped in parentheses to protect them from precedence issues, but
27+
* that is not always possible.
28+
*/
29+
predicate parameterPrecedenceUnprotected(int index) {
30+
// Check if the parameter is used in a way that requires parentheses
31+
exists(string parameter | parameter = getParameter(index) |
32+
// Finds any occurence of the parameter that is not preceded by, or followed by, either a
33+
// parenthesis or the '#' token operator.
34+
//
35+
// Note the following cases:
36+
// - "(x + 1)" is preceded by a parenthesis, but not followed by one, so SHOULD be matched.
37+
// - "x # 1" is followed by "#" (though not preceded by #) and SHOULD be matched.
38+
// - "(1 + x)" is followed by a parenthesis, but not preceded by one, so SHOULD be matched.
39+
// - "1 # x" is preceded by "#" (though not followed by #) and SHOULD NOT be matched.
40+
//
41+
// So the regex is structured as follows:
42+
// - paramMatch: Matches the parameter at a word boundary, with optional whitespace
43+
// - notHashed: Finds parameters not used with a leading # operator.
44+
// - The final regex finds cases of `notHashed` that are not preceded by a parenthesis,
45+
// and cases of `notHashed` that are not followed by a parenthesis.
46+
//
47+
// Therefore, a parameter with parenthesis on both sides is not matched, a parameter with
48+
// parenthesis missing on one or both sides is only matched if there is no leading or trailing
49+
// ## operator.
50+
exists(string noBeforeParen, string noAfterParen, string paramMatch, string notHashed |
51+
// Not preceded by a parenthesis
52+
noBeforeParen = "(?<!\\(\\s*)" and
53+
// Not followed by a parenthesis
54+
noAfterParen = "(?!\\s*\\))" and
55+
// Parameter at word boundary in optional whitespace
56+
paramMatch = "\\s*\\b" + parameter + "\\b\\s*" and
57+
// A parameter is ##'d if it is preceded or followed by the # operator.
58+
notHashed = "(?<!#)" + paramMatch and
59+
// Parameter is used without a leading or trailing parenthesis, and without #.
60+
getBody()
61+
.regexpMatch(".*(" + noBeforeParen + notHashed + "|" + notHashed + noAfterParen + ").*")
62+
)
63+
)
64+
}
2265
}
2366

2467
newtype TMacroOperator =

‎cpp/common/src/codingstandards/cpp/MatchingParenthesis.qll

Copy file name to clipboardExpand all lines: cpp/common/src/codingstandards/cpp/MatchingParenthesis.qll
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ module MatchingParenthesis<InputString Input> {
6161
occurrence = prevOccurrence + 1
6262
) else (
6363
token = TNotParen() and
64-
exists(inputStr.regexpFind("\\(|\\)", prevOccurrence + 1, endPos)) and
64+
exists(inputStr.regexpFind("\\(|\\)|$", prevOccurrence + 1, endPos)) and
6565
occurrence = prevOccurrence
6666
)
6767
)
+61Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
//** THIS FILE IS AUTOGENERATED, DO NOT MODIFY DIRECTLY. **/
2+
import cpp
3+
import RuleMetadata
4+
import codingstandards.cpp.exclusions.RuleMetadata
5+
6+
newtype Preprocessor2Query =
7+
TInvalidIncludeDirectiveQuery() or
8+
TUnparenthesizedMacroArgumentQuery() or
9+
TDisallowedUseOfPragmaQuery()
10+
11+
predicate isPreprocessor2QueryMetadata(Query query, string queryId, string ruleId, string category) {
12+
query =
13+
// `Query` instance for the `invalidIncludeDirective` query
14+
Preprocessor2Package::invalidIncludeDirectiveQuery() and
15+
queryId =
16+
// `@id` for the `invalidIncludeDirective` query
17+
"cpp/misra/invalid-include-directive" and
18+
ruleId = "RULE-19-2-2" and
19+
category = "required"
20+
or
21+
query =
22+
// `Query` instance for the `unparenthesizedMacroArgument` query
23+
Preprocessor2Package::unparenthesizedMacroArgumentQuery() and
24+
queryId =
25+
// `@id` for the `unparenthesizedMacroArgument` query
26+
"cpp/misra/unparenthesized-macro-argument" and
27+
ruleId = "RULE-19-3-4" and
28+
category = "required"
29+
or
30+
query =
31+
// `Query` instance for the `disallowedUseOfPragma` query
32+
Preprocessor2Package::disallowedUseOfPragmaQuery() and
33+
queryId =
34+
// `@id` for the `disallowedUseOfPragma` query
35+
"cpp/misra/disallowed-use-of-pragma" and
36+
ruleId = "RULE-19-6-1" and
37+
category = "advisory"
38+
}
39+
40+
module Preprocessor2Package {
41+
Query invalidIncludeDirectiveQuery() {
42+
//autogenerate `Query` type
43+
result =
44+
// `Query` type for `invalidIncludeDirective` query
45+
TQueryCPP(TPreprocessor2PackageQuery(TInvalidIncludeDirectiveQuery()))
46+
}
47+
48+
Query unparenthesizedMacroArgumentQuery() {
49+
//autogenerate `Query` type
50+
result =
51+
// `Query` type for `unparenthesizedMacroArgument` query
52+
TQueryCPP(TPreprocessor2PackageQuery(TUnparenthesizedMacroArgumentQuery()))
53+
}
54+
55+
Query disallowedUseOfPragmaQuery() {
56+
//autogenerate `Query` type
57+
result =
58+
// `Query` type for `disallowedUseOfPragma` query
59+
TQueryCPP(TPreprocessor2PackageQuery(TDisallowedUseOfPragmaQuery()))
60+
}
61+
}

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

Copy file name to clipboardExpand all lines: cpp/common/src/codingstandards/cpp/exclusions/cpp/RuleMetadata.qll
+3Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import OrderOfEvaluation
4141
import OutOfBounds
4242
import Pointers
4343
import Preprocessor
44+
import Preprocessor2
4445
import Representation
4546
import Scope
4647
import SideEffects1
@@ -96,6 +97,7 @@ newtype TCPPQuery =
9697
TOutOfBoundsPackageQuery(OutOfBoundsQuery q) or
9798
TPointersPackageQuery(PointersQuery q) or
9899
TPreprocessorPackageQuery(PreprocessorQuery q) or
100+
TPreprocessor2PackageQuery(Preprocessor2Query q) or
99101
TRepresentationPackageQuery(RepresentationQuery q) or
100102
TScopePackageQuery(ScopeQuery q) or
101103
TSideEffects1PackageQuery(SideEffects1Query q) or
@@ -151,6 +153,7 @@ predicate isQueryMetadata(Query query, string queryId, string ruleId, string cat
151153
isOutOfBoundsQueryMetadata(query, queryId, ruleId, category) or
152154
isPointersQueryMetadata(query, queryId, ruleId, category) or
153155
isPreprocessorQueryMetadata(query, queryId, ruleId, category) or
156+
isPreprocessor2QueryMetadata(query, queryId, ruleId, category) or
154157
isRepresentationQueryMetadata(query, queryId, ruleId, category) or
155158
isScopeQueryMetadata(query, queryId, ruleId, category) or
156159
isSideEffects1QueryMetadata(query, queryId, ruleId, category) or
+24Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/**
2+
* @id cpp/misra/invalid-include-directive
3+
* @name RULE-19-2-2: The #include directive shall be followed by either a <filename> or "filename" sequence
4+
* @description Include directives shall only use the <filename> or "filename" forms.
5+
* @kind problem
6+
* @precision very-high
7+
* @problem.severity error
8+
* @tags external/misra/id/rule-19-2-2
9+
* scope/single-translation-unit
10+
* maintainability
11+
* correctness
12+
* external/misra/enforcement/decidable
13+
* external/misra/obligation/required
14+
*/
15+
16+
import cpp
17+
import codingstandards.cpp.misra
18+
19+
from Include include
20+
where
21+
not isExcluded(include, Preprocessor2Package::invalidIncludeDirectiveQuery()) and
22+
// Check for < followed by (not >)+ followed by >, or " followed by (not ")+ followed by ".
23+
not include.getIncludeText().trim().regexpMatch("^(<[^>]+>|\"[^\"]+\")$")
24+
select include, "Non-compliant #include directive text '" + include.getHead() + "'."
+187Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
/**
2+
* @id cpp/misra/unparenthesized-macro-argument
3+
* @name RULE-19-3-4: Parentheses shall be used to ensure macro arguments are expanded appropriately
4+
* @description Expanded macro arguments shall be enclosed in parentheses to ensure the resulting
5+
* expressions have the expected precedence and order of operations.
6+
* @kind problem
7+
* @precision very-high
8+
* @problem.severity error
9+
* @tags external/misra/id/rule-19-3-4
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+
import codingstandards.cpp.Macro
20+
import codingstandards.cpp.MatchingParenthesis
21+
import codeql.util.Boolean
22+
23+
/**
24+
* This regex is used to find macro arguments that appear to have critical operators in them, before
25+
* we do the expensive process of parsing them to look for parenthesis.
26+
*/
27+
pragma[noinline]
28+
string criticalOperatorRegex() {
29+
result =
30+
".*(" +
31+
concat(string op |
32+
op in [
33+
"\\*=?", "/=?", "%=?", "\\+=?", "-=?", "<<?=?", ">>?=?", "==?", "!=", "&&?=?", "\\^/?",
34+
"\\|\\|?=?", "\\?"
35+
]
36+
|
37+
op, "|"
38+
) + ").*"
39+
}
40+
41+
/**
42+
* Whether a string appears to contain a critical operator.
43+
*/
44+
bindingset[input]
45+
predicate hasCriticalOperator(string input) { input.regexpMatch(criticalOperatorRegex()) }
46+
47+
/**
48+
* A critical operator is an operator with "level" between 13 and 2, according to the MISRA C++
49+
* standard. This includes from the "multiplicative" level (13) to the "conditional" level (2).
50+
*/
51+
class CriticalOperatorExpr extends Expr {
52+
string operator;
53+
54+
CriticalOperatorExpr() {
55+
operator = this.(BinaryOperation).getOperator()
56+
or
57+
this instanceof ConditionalExpr and operator = "?"
58+
or
59+
operator = this.(Assignment).getOperator()
60+
}
61+
62+
string getOperator() { result = operator }
63+
}
64+
65+
/**
66+
* An invocation of a macro that has a parameter that is not precedence-protected with parentheses,
67+
* and that produces a critical operator expression.
68+
*
69+
* This class is used in two passes. Firstly, with `hasRiskyParameter`, to find the macro paramaters
70+
* that should be parsed for parenthesis. Secondly, with `hasNonCompliantParameter`, to parse the
71+
* risky parameters and attempt to match the produced AST to an unparenthesized occurence of that
72+
* operator in the argument text.
73+
*
74+
* For a given macro invocation to be considered risky, it must
75+
* - The macro must have a parameter that is not precedence-protected with parentheses.
76+
* - The macro must produce a critical operator expression.
77+
* - The macro must produce only expressions, statements, or variable declarations with initializers.
78+
*
79+
* For a risky macro to be non-compliant, it must hold for some values of the predicate
80+
* `hasNonCompliantParameter`.
81+
*/
82+
class RiskyMacroInvocation extends MacroInvocation {
83+
FunctionLikeMacro macro;
84+
string riskyParamName;
85+
int riskyParamIdx;
86+
87+
RiskyMacroInvocation() {
88+
macro = getMacro() and
89+
// The parameter is not precedence-protected with parentheses in the macro body.
90+
macro.parameterPrecedenceUnprotected(riskyParamIdx) and
91+
riskyParamName = macro.getParameter(riskyParamIdx) and
92+
// This macro invocation produces a critical operator expression.
93+
getAGeneratedElement() instanceof CriticalOperatorExpr and
94+
// It seems to generate an expression, statement, or variable declaration with initializer.
95+
forex(Element e | e = getAGeneratedElement() |
96+
e instanceof Expr
97+
or
98+
e instanceof Stmt
99+
or
100+
e.(Variable).getInitializer().getExpr() = getAGeneratedElement()
101+
or
102+
e.(VariableDeclarationEntry).getDeclaration().getInitializer().getExpr() =
103+
getAGeneratedElement()
104+
)
105+
}
106+
107+
/**
108+
* A stage 1 pass used to find macro parameters that are not precedence-protected, and have a
109+
* critical operator in them, and therefore need to be parsed to check for parenthesis at the
110+
* macro call-site, which is expensive.
111+
*/
112+
predicate hasRiskyParameter(string name, int index, string value) {
113+
name = riskyParamName and
114+
index = riskyParamIdx and
115+
value = getExpandedArgument(riskyParamIdx) and
116+
hasCriticalOperator(value)
117+
}
118+
119+
/**
120+
* A stage 2 pass that occurs after risky parameters have been parsed, to check for parenthesis at the macro
121+
* call-site.
122+
*
123+
* For a given macro argument to be flagged, it must:
124+
* - be risky as determined by the characteristic predicate (produce a critical operator and only
125+
* expressions, statements, etc).
126+
* - be flagged by stage 1 as a risky parameter (i.e. it must have a critical operator in it and
127+
* correspond to a macro parameter that is not precedence-protected with parentheses)
128+
* - there must be a top-level text node that contains the operator in the argument string
129+
* - the operator cannot be the first character in the string (i.e. it should not look like a
130+
* unary - or +)
131+
* - the operator cannot exist inside a generated string literal
132+
* - the operator existence of the operator should not be as a substring of "->", "++", or "--"
133+
* operators.
134+
*
135+
* The results of this predicate should be flagged by the query.
136+
*/
137+
predicate hasNonCompliantParameter(string name, int index, string value, string operator) {
138+
hasRiskyParameter(name, index, value) and
139+
exists(
140+
ParsedRoot parsedRoot, ParsedText topLevelText, string text, CriticalOperatorExpr opExpr,
141+
int opIndex
142+
|
143+
parsedRoot.getInputString() = value and
144+
(topLevelText.getParent() = parsedRoot or topLevelText = parsedRoot) and
145+
text = topLevelText.getText().trim() and
146+
opExpr = getAGeneratedElement() and
147+
operator = opExpr.getOperator() and
148+
opIndex = text.indexOf(operator) and
149+
// Ignore "->", "++", and "--" operators.
150+
not [text.substring(opIndex - 1, opIndex + 1), text.substring(opIndex, opIndex + 2)] =
151+
["--", "++", "->"] and
152+
// Ignore operators inside string literals.
153+
not exists(Literal l |
154+
l = getAGeneratedElement() and
155+
exists(l.getValue().indexOf(operator))
156+
) and
157+
// A leading operator is probably unary and not a problem.
158+
(opIndex > 0 or topLevelText.getChildIdx() > 0)
159+
)
160+
}
161+
}
162+
163+
/**
164+
* A string class that is used to determine what macro arguments will be parsed.
165+
*
166+
* This should be a reasonably small set of strings, as parsing is expensive.
167+
*/
168+
class RiskyMacroArgString extends string {
169+
RiskyMacroArgString() { any(RiskyMacroInvocation mi).hasRiskyParameter(_, _, this) }
170+
}
171+
172+
// Import `ParsedRoot` etc for the parsed macro arguments.
173+
import MatchingParenthesis<RiskyMacroArgString>
174+
175+
from
176+
RiskyMacroInvocation mi, FunctionLikeMacro m, string paramName, string criticalOperator,
177+
int paramIndex, string argumentString
178+
where
179+
not isExcluded([m.(Element), mi.(Element)],
180+
Preprocessor2Package::unparenthesizedMacroArgumentQuery()) and
181+
mi.getMacro() = m and
182+
mi.hasNonCompliantParameter(paramName, paramIndex, argumentString, criticalOperator)
183+
select mi,
184+
"Macro argument " + paramIndex + " (with expanded value '" + argumentString + "') contains a" +
185+
" critical operator '" + criticalOperator +
186+
"' that is not parenthesized, but macro $@ argument '" + paramName +
187+
"' is not precedence-protected with parenthesis.", m, m.getName()
+32Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/**
2+
* @id cpp/misra/disallowed-use-of-pragma
3+
* @name RULE-19-6-1: The #pragma directive and the _Pragma operator should not be used
4+
* @description Preprocessor pragma directives are implementation-defined, and should not be used to
5+
* maintain code portability.
6+
* @kind problem
7+
* @precision very-high
8+
* @problem.severity error
9+
* @tags external/misra/id/rule-19-6-1
10+
* scope/single-translation-unit
11+
* maintainability
12+
* external/misra/enforcement/decidable
13+
* external/misra/obligation/advisory
14+
*/
15+
16+
import cpp
17+
import codingstandards.cpp.misra
18+
19+
from PreprocessorDirective pragma, string kind
20+
where
21+
not isExcluded(pragma, Preprocessor2Package::disallowedUseOfPragmaQuery()) and
22+
(
23+
pragma instanceof PreprocessorPragma and
24+
kind = "#pragma directive '" + pragma.getHead() + "'"
25+
or
26+
exists(string headOrBody, string pragmaOperand |
27+
headOrBody = [pragma.getHead(), pragma.(Macro).getBody()] and
28+
pragmaOperand = headOrBody.regexpCapture(".*\\b(_Pragma\\b\\s*\\([^\\)]+\\)).*", 1) and
29+
kind = "_Pragma operator used: '" + pragmaOperand + "'"
30+
)
31+
)
32+
select pragma, "Non-compliant " + kind + "."
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
| test.cpp:6:1:6:20 | #include STRING_PATH | Non-compliant #include directive text 'STRING_PATH'. |
2+
| test.cpp:10:1:10:16 | #include QSTRING | Non-compliant #include directive text 'QSTRING'. |
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
rules/RULE-19-2-2/InvalidIncludeDirective.ql

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.