-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Expand file tree
/
Copy pathDataFlow.qll
More file actions
393 lines (326 loc) · 12.6 KB
/
DataFlow.qll
File metadata and controls
393 lines (326 loc) · 12.6 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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
/**
* Experimental library for reasoning about data flow.
*
* Current limitations:
* - Global flow does not reason about subclassing, overriding, and dispatch
* - `this`, `result`, and local field variables are treated less precisely
* than regular variables (see VarScoping.qll)
* - Polarity is not tracked, that is, global flow does not care about negation at all.
*/
private import codeql_ql.ast.Ast
private import internal.NodesInternal
private import internal.DataFlowNumbering
private import internal.LocalFlow as LocalFlow
private import internal.GlobalFlow as GlobalFlow
/**
* An expression or variable in a formula, including some additional nodes
* that are not part of the AST.
*
* Nodes that are locally bound together by equalities are clustered into a "super node",
* which can be accessed using `getSuperNode()`. There is usually no reason to use `Node` directly
* other than to reason about what kind of node is contained in a super node.
*
* To reason about global data flow, use `SuperNode.track()`.
*/
class Node extends TNode {
/** Gets a string representation of this element. */
string toString() { none() } // overridden in subclasses
/** Gets the location of element. */
Location getLocation() { none() } // overridden in subclasses
/**
* Gets the underlying `Expr` or `VarDef` node, if this is an `AstNodeNode`.
*/
AstNode asAstNode() { astNode(result) = this }
/**
* Gets the predicate containing this data-flow node.
*
* All data-flow nodes belong in exactly one predicate.
* TODO: select clauses
*/
Predicate getEnclosingPredicate() { none() } // overridden in subclasses
/**
* Gets the collection of data-flow nodes locally bound by equalities, represented
* by a "super node".
*
* Super nodes are the medium through which to propagate data-flow information globally.
*/
SuperNode getSuperNode() { result.getANode() = this }
}
/**
* A data-flow node based an `Expr` or `VarDef` AST node.
*/
class AstNodeNode extends Node, MkAstNodeNode {
private AstNode ast;
AstNodeNode() { this = MkAstNodeNode(ast) }
override string toString() { result = ast.toString() }
override Location getLocation() { result = ast.getLocation() }
/** Gets the AST node. */
AstNode getAstNode() { result = ast }
override Predicate getEnclosingPredicate() { result = ast.getEnclosingPredicate() }
}
/**
* Gets the data-flow node corresponding to the given AST node.
*/
pragma[inline]
Node astNode(AstNode node) { result = MkAstNodeNode(node) }
/**
* A data-flow node representing a variable within a specific scope.
*/
class ScopedVariableNode extends Node, MkScopedVariable {
private VarDef var;
private AstNode scope;
ScopedVariableNode() { this = MkScopedVariable(var, scope) }
override string toString() {
result =
"Variable '" + var.getName() + "' scoped to " + scope.getLocation().getStartLine() + ":" +
scope.getLocation().getStartColumn()
}
override Location getLocation() { result = scope.getLocation() }
/** Gets the variable being refined to a specific scope. */
VarDef getVariable() { result = var }
/** Gets the scope to which this variable has been refined. */
AstNode getScope() { result = scope }
override Predicate getEnclosingPredicate() { result = var.getEnclosingPredicate() }
}
/**
* Gets the data-flow node corresponding to `var` restricted to `scope`.
*/
pragma[inline]
Node scopedVariable(VarDef var, AstNode scope) { result = MkScopedVariable(var, scope) }
/**
* A data-flow node representing `this` within a class predicate, charpred, or newtype branch.
*/
class ThisNode extends Node, MkThisNode {
private Predicate pred;
ThisNode() { this = MkThisNode(pred) }
override string toString() { result = "'this' in " + pred.getName() }
override Location getLocation() { result = pred.getLocation() }
/** Gets the class predicate, charpred, or newtype branch whose 'this' parameter is represented by this node. */
Predicate getPredicate() { result = pred }
override Predicate getEnclosingPredicate() { result = pred }
}
/**
* Gets the data-flow node representing `this` within the given class predicate, charpred, or newtype branch.
*/
pragma[inline]
Node thisNode(Predicate pred) { result = MkThisNode(pred) }
/**
* A data-flow node representing `result` within a predicate that has a result.
*/
class ResultNode extends Node, MkResultNode {
private Predicate pred;
ResultNode() { this = MkResultNode(pred) }
override string toString() { result = "'result' in " + pred.getName() }
override Location getLocation() { result = pred.getLocation() }
/** Gets the predicate whose 'result' parameter is represented by this node. */
Predicate getPredicate() { result = pred }
override Predicate getEnclosingPredicate() { result = pred }
}
/**
* Gets the data-flow node representing `result` within the given predicate.
*/
pragma[inline]
Node resultNode(Predicate pred) { result = MkResultNode(pred) }
/**
* A data-flow node representing the view of a field in the enclosing class, as seen
* from a charpred or class predicate.
*/
class FieldNode extends Node, MkFieldNode {
private Predicate pred;
private FieldDecl fieldDecl;
FieldNode() { this = MkFieldNode(pred, fieldDecl) }
/** Gets the member predicate or charpred for which this node represents access to the field. */
Predicate getPredicate() { result = pred }
/** Gets the declaration of the field. */
FieldDecl getFieldDeclaration() { result = fieldDecl }
/** Gets the name of the field. */
string getFieldName() { result = fieldDecl.getName() }
override string toString() { result = "'" + this.getFieldName() + "' in " + pred.getName() }
override Location getLocation() { result = pred.getLocation() }
override Predicate getEnclosingPredicate() { result = pred }
}
/**
* Gets the data-flow node representing the given predicate's view of the given field
* in the enclosing class.
*/
pragma[inline]
Node fieldNode(Predicate pred, FieldDecl fieldDecl) { result = MkFieldNode(pred, fieldDecl) }
/**
* A collection of data-flow nodes in the same predicate, locally bound by equalities.
*
* To reason about global data flow, use `SuperNode.track()`.
*/
class SuperNode extends LocalFlow::TSuperNode {
private int repr;
SuperNode() { this = LocalFlow::MkSuperNode(repr) }
/** Gets a data-flow node that is part of this super node. */
Node getANode() { LocalFlow::getRepr(result) = repr }
/** Gets an AST node from any of the nodes in this super node. */
AstNode asAstNode() { result = this.getANode().asAstNode() }
/**
* Gets a single node from this super node.
*
* The node is arbitrary and the caller should not rely on how the node is chosen.
* The node is currently chosen such that:
* - An `AstNodeNode` is preferred over other nodes.
* - A node occurring earlier is preferred over one occurring later.
*/
Node getArbitraryRepr() {
result = min(Node n | n = this.getANode() | n order by getInternalId(n))
}
/**
* Gets the predicate containing all nodes that are part of this super node.
*/
Predicate getEnclosingPredicate() { result = this.getANode().getEnclosingPredicate() }
/** Gets a string representation of this super node. */
string toString() {
exists(int c |
c = strictcount(this.getANode()) and
result = "Super node of " + c + " nodes in " + this.getEnclosingPredicate().getName()
)
}
/** Gets the location of an arbitrary node in this super node. */
Location getLocation() { result = this.getArbitraryRepr().getLocation() }
/** Gets any member call whose receiver is in the same super node. */
MemberCall getALocalMemberCall() { superNode(result.getBase()) = this }
/** Gets any member call whose receiver is in the same super node. */
MemberCall getALocalMemberCall(string name) {
result = this.getALocalMemberCall() and
result.getMemberName() = name
}
/**
* Gets a node that this node may "flow to" after one step.
*
* Basic usage of `track()` to track some expressions looks like this:
* ```
* DataFlow::SuperNode myThing(DataFlow::Tracker t) {
* t.start() and
* result = DataFlow::superNode(< some ast node >)
* or
* exists (DataFlow::Tracker t2 |
* result = myThing(t2).track(t2, t)
* )
* }
*
* DataFlow::SuperNode myThing() { result = myThing(DataFlow::Tracker::end()) }
* ```
*/
pragma[inline]
SuperNode track(Tracker t1, Tracker t2) {
// Return state -> return state
// Store the return edge in t2
not t1.hasCall() and
GlobalFlow::directedEdgeSuper(result, this, t2)
or
// Call state or initial state -> call state
t1.hasCallOrIsStart() and
t2.hasCall() and
GlobalFlow::directedEdgeSuper(this, result, _)
or
// Return state -> call state
// The last-used return edge must not be used as the initial call edge
// (doing so would allow returning out of a disjunction and into another branch of that disjunction)
not t1.hasCall() and
t2.hasCall() and
exists(GlobalFlow::EdgeLabel edge |
GlobalFlow::directedEdgeSuper(this, result, edge) and
edge != t1
)
}
/**
* Gets node containing a string flowing to this node via `t`.
*/
cached
private string getAStringValue(Tracker t) {
t.start() and
result = this.asAstNode().(String).getValue()
or
exists(SuperNode pred, Tracker t2 |
this = pred.track(t2, t) and
result = pred.getAStringValue(t2)
)
or
// Step through calls to a few built-ins that don't cause a blow-up
exists(SuperNode pred, string methodName, string oldValue |
this.asAstNode() = pred.getALocalMemberCall(methodName) and
oldValue = pred.getAStringValue(t)
|
methodName = "toLowerCase" and
result = oldValue.toLowerCase()
or
methodName = "toUpperCase" and
result = oldValue.toUpperCase()
)
}
/** Gets a string constant that may flow here (possibly from a caller context). */
pragma[inline]
string getAStringValue() { result = this.getAStringValue(Tracker::end()) }
/** Gets a string constant that may flow here, possibly out of callees, but not from caller contexts. */
pragma[inline]
string getAStringValueNoCall() { result = this.getAStringValue(Tracker::endNoCall()) }
/**
* Gets a string constant that may flow here, which can safely be combined with another
* value that was tracked here with `otherT`.
*
* This is under-approximate and will fail to accept valid matches when both values
* came in from the same chain of calls.
*/
bindingset[otherT]
string getAStringValueForContext(Tracker otherT) {
exists(Tracker stringT |
result = this.getAStringValue(stringT) and
otherT.isSafeToCombineWith(stringT)
)
}
}
/** Gets the super node for the given AST node. */
pragma[inline]
SuperNode superNode(AstNode node) { result = astNode(node).getSuperNode() }
/**
* A summary of the steps needed to reach a node in the global data flow graph,
* to be used in combination with `SuperNode.track`.
*/
class Tracker extends GlobalFlow::TEdgeLabelOrTrackerState {
/** Holds if this is the starting point, that is, the summary of the empty path. */
predicate start() { this = GlobalFlow::MkNoEdge() }
/** Holds if a call step has been used (possibly preceded by return steps). */
predicate hasCall() { this = GlobalFlow::MkHasCall() }
/** Holds if either `start()` or `hasCall()` holds */
predicate hasCallOrIsStart() { this.start() or this.hasCall() }
/**
* Holds if the two trackers are safe to combine, in the sense that
* they don't make contradictory assumptions what context they're in.
*
* This is approximate and will reject any pair of trackers that have
* both used a call or locally came from the same disjunction.
*/
pragma[inline]
predicate isSafeToCombineWith(Tracker other) {
not (
// Both values came from a call, they could come from different call sites.
this.hasCall() and
other.hasCall()
or
// Both values came from the same disjunction, they could come from different branches.
this = other and
this instanceof GlobalFlow::MkDisjunction
)
}
/** Gets a string representation of this element. */
string toString() {
this instanceof GlobalFlow::MkNoEdge and
result = "Tracker in initial state"
or
this instanceof GlobalFlow::MkHasCall and
result = "Tracker with calls"
or
this instanceof GlobalFlow::EdgeLabel and
result = "Tracker with return step out of " + this.(GlobalFlow::EdgeLabel).toString()
}
}
module Tracker {
/** Gets a valid end-point for tracking. */
Tracker end() { any() }
/** Gets a valid end-point for tracking where no calls were used. */
Tracker endNoCall() { not result.hasCall() }
}