-
Notifications
You must be signed in to change notification settings - Fork 77
Expand file tree
/
Copy pathMacro.qll
More file actions
147 lines (129 loc) · 5.83 KB
/
Macro.qll
File metadata and controls
147 lines (129 loc) · 5.83 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
import cpp
/**
* Macros with parentheses, e.g. `#define MACRO(x) (x * 2)`.
*
* Note that this includes macros with empty parameter lists, e.g. `#define MACRO() 42`.
*/
class FunctionLikeMacro extends Macro {
FunctionLikeMacro() { this.getHead().regexpMatch("[_a-zA-Z0-9]+\\s*\\([^\\)]*?\\)") }
string getParameter(int i) {
result =
this.getHead()
.regexpCapture("[_a-zA-Z0-9]+\\s*\\(([^\\)]*)\\)", 1)
.splitAt(",", i)
.trim()
.replaceAll("...", "") and
not result = ""
}
string getAParameter() { result = getParameter(_) }
int getAParameterUse(int index) {
exists(string parameter | parameter = getParameter(index) |
// Find identifier tokens in the program that match the parameter name
exists(this.getBody().regexpFind("\\#?\\b" + parameter + "\\b", _, result))
)
}
/**
* Holds if the parameter is used in a way that may make it vulnerable to precedence issues.
*
* Typically, parameters are wrapped in parentheses to protect them from precedence issues, but
* that is not always possible.
*/
predicate parameterPrecedenceUnprotected(int index) {
// Check if the parameter is used in a way that requires parentheses
exists(string parameter | parameter = getParameter(index) |
// Finds any occurence of the parameter that is not preceded by, or followed by, either a
// parenthesis or the '#' token operator.
//
// Note the following cases:
// - "(x + 1)" is preceded by a parenthesis, but not followed by one, so SHOULD be matched.
// - "x # 1" is followed by "#" (though not preceded by #) and SHOULD be matched.
// - "(1 + x)" is followed by a parenthesis, but not preceded by one, so SHOULD be matched.
// - "1 # x" is preceded by "#" (though not followed by #) and SHOULD NOT be matched.
//
// So the regex is structured as follows:
// - paramMatch: Matches the parameter at a word boundary, with optional whitespace
// - notHashed: Finds parameters not used with a leading # operator.
// - The final regex finds cases of `notHashed` that are not preceded by a parenthesis,
// and cases of `notHashed` that are not followed by a parenthesis.
//
// Therefore, a parameter with parenthesis on both sides is not matched, a parameter with
// parenthesis missing on one or both sides is only matched if there is no leading or trailing
// ## operator.
exists(string noBeforeParen, string noAfterParen, string paramMatch, string notHashed |
// Not preceded by a parenthesis
noBeforeParen = "(?<!\\(\\s*)" and
// Not followed by a parenthesis
noAfterParen = "(?!\\s*\\))" and
// Parameter at word boundary in optional whitespace
paramMatch = "\\s*\\b" + parameter + "\\b\\s*" and
// A parameter is ##'d if it is preceded or followed by the # operator.
notHashed = "(?<!#)" + paramMatch and
// Parameter is used without a leading or trailing parenthesis, and without #.
getBody()
.regexpMatch(".*(" + noBeforeParen + notHashed + "|" + notHashed + noAfterParen + ").*")
)
)
}
}
newtype TMacroOperator =
TTokenPastingOperator(FunctionLikeMacro m, string operand, int operatorOffset, int operandOffset) {
m.getAParameter() = operand and
(
exists(string match |
match = m.getBody().regexpFind("#{2}\\s*" + operand, _, operatorOffset)
|
operandOffset = operatorOffset + match.indexOf(operand)
)
or
exists(string match | match = m.getBody().regexpFind(operand + "\\s*#{2}", _, operandOffset) |
operatorOffset = operandOffset + match.indexOf("##")
)
)
} or
TStringizingOperator(FunctionLikeMacro m, string operand, int operatorOffset, int operandOffset) {
operand = m.getAParameter() and
exists(string match |
match = m.getBody().regexpFind("(?<!#)#\\s*" + operand, _, operatorOffset)
|
operandOffset = operatorOffset + match.indexOf(operand)
)
}
class TokenPastingOperator extends TTokenPastingOperator {
string getOperand() { this = TTokenPastingOperator(_, result, _, _) }
FunctionLikeMacro getMacro() { this = TTokenPastingOperator(result, _, _, _) }
int getOffset() { this = TTokenPastingOperator(_, _, result, _) }
string toString() { result = getMacro().toString() }
}
class StringizingOperator extends TStringizingOperator {
string getOperand() { this = TStringizingOperator(_, result, _, _) }
FunctionLikeMacro getMacro() { this = TStringizingOperator(result, _, _, _) }
int getOffset() { this = TStringizingOperator(_, _, result, _) }
string toString() { result = getMacro().toString() }
}
pragma[noinline]
predicate isMacroInvocationLocation(MacroInvocation mi, File f, int startChar, int endChar) {
mi.getActualLocation().charLoc(f, startChar, endChar)
}
/** A macro within the source location of this project. */
class UserProvidedMacro extends Macro {
UserProvidedMacro() {
exists(this.getFile().getRelativePath()) and
// Exclude macros in our standard library header stubs for tests, because qltest sets the source
// root to the qlpack root, which means our stubs all look like source files.
//
// This may affect "real" code as well, if it happens to be at this path, but given the name
// I think it's likely that we'd want that to be the case anyway.
not this.getFile().getRelativePath().substring(0, "includes/standard-library".length()) =
"includes/standard-library"
}
}
/** A macro defined within a library used by this project. */
class LibraryMacro extends Macro {
LibraryMacro() { not this instanceof UserProvidedMacro }
}
/**
* A macro which is suggestive that it is used to determine the precision of an integer.
*/
class PrecisionMacro extends Macro {
PrecisionMacro() { this.getName().toLowerCase().matches("precision") }
}