forked from github/codeql
-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathVariableImpl.qll
More file actions
686 lines (594 loc) · 21.6 KB
/
VariableImpl.qll
File metadata and controls
686 lines (594 loc) · 21.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
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
private import rust
private import codeql.rust.controlflow.ControlFlowGraph
private import codeql.rust.elements.internal.generated.ParentChild
private import codeql.rust.elements.internal.PathImpl::Impl as PathImpl
private import codeql.rust.elements.internal.PathExprBaseImpl::Impl as PathExprBaseImpl
private import codeql.rust.elements.internal.FormatTemplateVariableAccessImpl::Impl as FormatTemplateVariableAccessImpl
private import codeql.util.DenseRank
module Impl {
/**
* A variable scope. Either a block `{ ... }`, the guard/rhs
* of a match arm, or the body of a closure.
*/
abstract class VariableScope extends AstNode { }
class BlockExprScope extends VariableScope, BlockExpr { }
abstract class MatchArmScope extends VariableScope {
MatchArm arm;
bindingset[arm]
MatchArmScope() { exists(arm) }
Pat getPat() { result = arm.getPat() }
}
class MatchArmExprScope extends MatchArmScope {
MatchArmExprScope() { this = arm.getExpr() }
}
class MatchArmGuardScope extends MatchArmScope {
MatchArmGuardScope() { this = arm.getGuard() }
}
class ClosureBodyScope extends VariableScope {
ClosureBodyScope() { this = any(ClosureExpr ce).getBody() }
}
private Pat getAPatAncestor(Pat p) {
(p instanceof IdentPat or p instanceof OrPat) and
exists(Pat p0 | result = p0.getParentPat() |
p0 = p
or
p0 = getAPatAncestor(p) and
not p0 instanceof OrPat
)
}
/** Gets the immediately enclosing `|` pattern of `p`, if any */
private OrPat getEnclosingOrPat(Pat p) { result = getAPatAncestor(p) }
/** Gets the outermost enclosing `|` pattern parent of `p`, if any. */
private OrPat getOutermostEnclosingOrPat(IdentPat p) {
result = getEnclosingOrPat+(p) and
not exists(getEnclosingOrPat(result))
}
/**
* Holds if `name` declares a variable named `text` at `definingNode`.
* Normally, `definingNode = name`, except in cases like
*
* ```rust
* match either {
* Either::Left(x) | Either::Right(x) => println!(x),
* }
* ```
*
* where `definingNode` is the entire `Either::Left(x) | Either::Right(x)`
* pattern.
*/
cached
private predicate variableDecl(AstNode definingNode, Name name, string text) {
Cached::ref() and
exists(SelfParam sp |
name = sp.getName() and
definingNode = name and
text = name.getText() and
// exclude self parameters from functions without a body as these are
// trait method declarations without implementations
not exists(Function f | not f.hasBody() and f.getParamList().getSelfParam() = sp)
)
or
exists(IdentPat pat |
name = pat.getName() and
(
definingNode = getOutermostEnclosingOrPat(pat)
or
not exists(getOutermostEnclosingOrPat(pat)) and definingNode = name
) and
text = name.getText() and
// exclude for now anything starting with an uppercase character, which may be a reference to
// an enum constant (e.g. `None`). This excludes static and constant variables (UPPERCASE),
// which we don't appear to recognize yet anyway. This also assumes programmers follow the
// naming guidelines, which they generally do, but they're not enforced.
not text.charAt(0).isUppercase() and
// exclude parameters from functions without a body as these are trait method declarations
// without implementations
not exists(Function f | not f.hasBody() and f.getParamList().getAParam().getPat() = pat) and
// exclude parameters from function pointer types (e.g. `x` in `fn(x: i32) -> i32`)
not exists(FnPtrTypeRepr fp | fp.getParamList().getParam(_).getPat() = pat)
)
}
/** A variable. */
class Variable extends MkVariable {
private AstNode definingNode;
private string text;
Variable() { this = MkVariable(definingNode, text) }
/** Gets the name of this variable as a string. */
string getText() { result = text }
/** Gets the location of this variable. */
Location getLocation() { result = definingNode.getLocation() }
/** Gets a textual representation of this variable. */
string toString() { result = this.getText() }
/** Gets an access to this variable. */
VariableAccess getAnAccess() { result.getVariable() = this }
/**
* Get the name of this variable.
*
* Normally, the name is unique, except when introduced in an or pattern.
*/
Name getName() { variableDecl(definingNode, result, text) }
/** Gets the block that encloses this variable, if any. */
BlockExpr getEnclosingBlock() { result = definingNode.getEnclosingBlock() }
/** Gets the `self` parameter that declares this variable, if any. */
SelfParam getSelfParam() { result.getName() = this.getName() }
/**
* Gets the pattern that declares this variable, if any.
*
* Normally, the pattern is unique, except when introduced in an or pattern:
*
* ```rust
* match either {
* Either::Left(x) | Either::Right(x) => println!(x),
* }
* ```
*/
IdentPat getPat() { result.getName() = this.getName() }
/** Gets the enclosing CFG scope for this variable declaration. */
CfgScope getEnclosingCfgScope() { result = definingNode.getEnclosingCfgScope() }
/** Gets the `let` statement that introduces this variable, if any. */
LetStmt getLetStmt() { this.getPat() = result.getPat() }
/** Gets the initial value of this variable, if any. */
Expr getInitializer() { result = this.getLetStmt().getInitializer() }
/** Holds if this variable is captured. */
predicate isCaptured() { this.getAnAccess().isCapture() }
/** Gets the parameter that introduces this variable, if any. */
cached
ParamBase getParameter() {
Cached::ref() and
result = this.getSelfParam()
or
result.(Param).getPat() = getAVariablePatAncestor(this)
}
/** Hold is this variable is mutable. */
predicate isMutable() { this.getPat().isMut() or this.getSelfParam().isMut() }
/** Hold is this variable is immutable. */
predicate isImmutable() { not this.isMutable() }
}
/**
* A path expression that may access a local variable. These are paths that
* only consist of a simple name (i.e., without generic arguments,
* qualifiers, etc.).
*/
private class VariableAccessCand extends PathExprBase {
string name_;
VariableAccessCand() {
name_ = this.(PathExpr).getPath().(PathImpl::IdentPath).getName()
or
this.(FormatTemplateVariableAccess).getName() = name_
}
string toString() { result = name_ }
string getName() { result = name_ }
}
private AstNode getAnAncestorInVariableScope(AstNode n) {
(
n instanceof Pat or
n instanceof VariableAccessCand or
n instanceof LetStmt or
n instanceof VariableScope
) and
exists(AstNode n0 |
result = getImmediateParent(n0) or
result = n0.(FormatTemplateVariableAccess).getArgument().getParent().getParent()
|
n0 = n
or
n0 = getAnAncestorInVariableScope(n) and
not n0 instanceof VariableScope
)
}
/** Gets the immediately enclosing variable scope of `n`. */
private VariableScope getEnclosingScope(AstNode n) { result = getAnAncestorInVariableScope(n) }
/**
* Get all the pattern ancestors of this variable up to an including the
* root of the pattern.
*/
private Pat getAVariablePatAncestor(Variable v) {
result = v.getPat()
or
exists(Pat mid |
mid = getAVariablePatAncestor(v) and
result = mid.getParentPat()
)
}
/**
* Holds if a parameter declares the variable `v` inside variable scope `scope`.
*/
private predicate parameterDeclInScope(Variable v, VariableScope scope) {
exists(Callable f |
v.getParameter() = f.getParamList().getAParamBase() and
scope = [f.(Function).getBody(), f.(ClosureExpr).getBody()]
)
}
/** A subset of `Element`s for which we want to compute pre-order numbers. */
private class RelevantElement extends Element {
RelevantElement() {
this instanceof VariableScope or
this instanceof VariableAccessCand or
this instanceof LetStmt or
getImmediateChild(this, _) instanceof RelevantElement
}
pragma[nomagic]
private RelevantElement getChild(int index) { result = getImmediateChild(this, index) }
pragma[nomagic]
private RelevantElement getImmediateChildMin(int index) {
// A child may have multiple positions for different accessors,
// so always use the first
result = this.getChild(index) and
index = min(int i | result = this.getChild(i) | i)
}
pragma[nomagic]
RelevantElement getImmediateChild(int index) {
result =
rank[index + 1](Element res, int i | res = this.getImmediateChildMin(i) | res order by i)
}
pragma[nomagic]
RelevantElement getImmediateLastChild() {
exists(int last |
result = this.getImmediateChild(last) and
not exists(this.getImmediateChild(last + 1))
)
}
}
/**
* Gets the pre-order numbering of `n`, where the immediately enclosing
* variable scope of `n` is `scope`.
*/
pragma[nomagic]
private int getPreOrderNumbering(VariableScope scope, RelevantElement n) {
n = scope and
result = 0
or
exists(RelevantElement parent |
not parent instanceof VariableScope
or
parent = scope
|
// first child of a previously numbered node
result = getPreOrderNumbering(scope, parent) + 1 and
n = parent.getImmediateChild(0)
or
// non-first child of a previously numbered node
exists(RelevantElement child, int i |
result = getLastPreOrderNumbering(scope, child) + 1 and
child = parent.getImmediateChild(i) and
n = parent.getImmediateChild(i + 1)
)
)
}
/**
* Gets the pre-order numbering of the _last_ node nested under `n`, where the
* immediately enclosing variable scope of `n` (and the last node) is `scope`.
*/
pragma[nomagic]
private int getLastPreOrderNumbering(VariableScope scope, RelevantElement n) {
exists(RelevantElement leaf |
result = getPreOrderNumbering(scope, leaf) and
leaf != scope and
(
not exists(leaf.getImmediateChild(_))
or
leaf instanceof VariableScope
)
|
n = leaf
or
n.getImmediateLastChild() = leaf and
not n instanceof VariableScope
)
or
exists(RelevantElement mid |
mid = n.getImmediateLastChild() and
result = getLastPreOrderNumbering(scope, mid) and
not mid instanceof VariableScope and
not n instanceof VariableScope
)
}
/**
* Holds if `v` is named `name` and is declared inside variable scope
* `scope`. The pre-order numbering of the binding site of `v`, amongst
* all nodes nester under `scope`, is `ord`.
*/
private predicate variableDeclInScope(Variable v, VariableScope scope, string name, int ord) {
name = v.getText() and
(
parameterDeclInScope(v, scope) and
ord = getPreOrderNumbering(scope, scope)
or
exists(Pat pat | pat = getAVariablePatAncestor(v) |
scope =
any(MatchArmScope arm |
arm.getPat() = pat and
ord = getPreOrderNumbering(scope, arm)
)
or
exists(LetStmt let |
let.getPat() = pat and
scope = getEnclosingScope(let) and
// for `let` statements, variables are bound _after_ the statement, i.e.
// not in the RHS
ord = getLastPreOrderNumbering(scope, let) + 1
)
or
exists(IfExpr ie, LetExpr let |
let.getPat() = pat and
ie.getCondition() = let and
scope = ie.getThen() and
ord = getPreOrderNumbering(scope, scope)
)
or
exists(ForExpr fe |
fe.getPat() = pat and
scope = fe.getLoopBody() and
ord = getPreOrderNumbering(scope, scope)
)
or
exists(WhileExpr we, LetExpr let |
let.getPat() = pat and
we.getCondition() = let and
scope = we.getLoopBody() and
ord = getPreOrderNumbering(scope, scope)
)
)
)
}
/**
* Holds if `cand` may access a variable named `name` at pre-order number `ord`
* in the variable scope `scope`.
*
* `nestLevel` is the number of nested scopes that need to be traversed
* to reach `scope` from `cand`.
*/
private predicate variableAccessCandInScope(
VariableAccessCand cand, VariableScope scope, string name, int nestLevel, int ord
) {
name = cand.getName() and
scope = [cand.(VariableScope), getEnclosingScope(cand)] and
ord = getPreOrderNumbering(scope, cand) and
nestLevel = 0
or
exists(VariableScope inner |
variableAccessCandInScope(cand, inner, name, nestLevel - 1, _) and
scope = getEnclosingScope(inner) and
// Use the pre-order number of the inner scope as the number of the access. This allows
// us to collapse multiple accesses in inner scopes to a single entity
ord = getPreOrderNumbering(scope, inner)
)
}
private newtype TDefOrAccessCand =
TDefOrAccessCandNestedFunction(Function f, BlockExprScope scope) {
f = scope.getStmtList().getAStatement()
} or
TDefOrAccessCandVariable(Variable v) or
TDefOrAccessCandVariableAccessCand(VariableAccessCand va)
/**
* A nested function declaration, variable declaration, or variable (or function)
* access candidate.
*
* In order to determine whether a candidate is an actual variable/function access,
* we rank declarations and candidates by their position in the AST.
*
* The ranking must take names into account, but also variable scopes; below a comment
* `rank(scope, name, i)` means that the declaration/access on the given line has rank
* `i` amongst all declarations/accesses inside variable scope `scope`, for name `name`:
*
* ```rust
* fn f() { // scope0
* let x = 0; // rank(scope0, "x", 0)
* use(x); // rank(scope0, "x", 1)
* let x = // rank(scope0, "x", 3)
* x + 1; // rank(scope0, "x", 2)
* let y = // rank(scope0, "y", 0)
* x; // rank(scope0, "x", 4)
*
* { // scope1
* use(x); // rank(scope1, "x", 0), rank(scope0, "x", 4)
* use(y); // rank(scope1, "y", 0), rank(scope0, "y", 1)
* let x = 2; // rank(scope1, "x", 1)
* use(x); // rank(scope1, "x", 2), rank(scope0, "x", 4)
* }
* }
* ```
*
* Function/variable declarations are only ranked in the scope that they bind into,
* while accesses candidates propagate outwards through scopes, as they may access
* declarations from outer scopes.
*
* For an access candidate with ranks `{ rank(scope_i, name, rnk_i) | i in I }` and
* declarations `d in D` with ranks `rnk(scope_d, name, rnk_d)`, the target is
* calculated as
* ```
* max_{i in I} (
* max_{d in D | scope_d = scope_i and rnk_d < rnk_i} (
* d
* )
* )
* ```
*
* i.e., its the nearest declaration before the access in the same (or outer) scope
* as the access.
*/
abstract private class DefOrAccessCand extends TDefOrAccessCand {
abstract string toString();
abstract Location getLocation();
pragma[nomagic]
abstract predicate rankBy(string name, VariableScope scope, int ord, int kind);
}
abstract private class NestedFunctionOrVariable extends DefOrAccessCand { }
private class DefOrAccessCandNestedFunction extends NestedFunctionOrVariable,
TDefOrAccessCandNestedFunction
{
private Function f;
private BlockExprScope scope_;
DefOrAccessCandNestedFunction() { this = TDefOrAccessCandNestedFunction(f, scope_) }
override string toString() { result = f.toString() }
override Location getLocation() { result = f.getLocation() }
override predicate rankBy(string name, VariableScope scope, int ord, int kind) {
// nested functions behave as if they are defined at the beginning of the scope
name = f.getName().getText() and
scope = scope_ and
ord = 0 and
kind = 0
}
}
private class DefOrAccessCandVariable extends NestedFunctionOrVariable, TDefOrAccessCandVariable {
private Variable v;
DefOrAccessCandVariable() { this = TDefOrAccessCandVariable(v) }
override string toString() { result = v.toString() }
override Location getLocation() { result = v.getLocation() }
override predicate rankBy(string name, VariableScope scope, int ord, int kind) {
variableDeclInScope(v, scope, name, ord) and
kind = 1
}
}
private class DefOrAccessCandVariableAccessCand extends DefOrAccessCand,
TDefOrAccessCandVariableAccessCand
{
private VariableAccessCand va;
DefOrAccessCandVariableAccessCand() { this = TDefOrAccessCandVariableAccessCand(va) }
override string toString() { result = va.toString() }
override Location getLocation() { result = va.getLocation() }
override predicate rankBy(string name, VariableScope scope, int ord, int kind) {
variableAccessCandInScope(va, scope, name, _, ord) and
kind = 2
}
}
private module DenseRankInput implements DenseRankInputSig2 {
class C1 = VariableScope;
class C2 = string;
class Ranked = DefOrAccessCand;
int getRank(VariableScope scope, string name, DefOrAccessCand v) {
v =
rank[result](DefOrAccessCand v0, int ord, int kind |
v0.rankBy(name, scope, ord, kind)
|
v0 order by ord, kind
)
}
}
/**
* Gets the rank of `v` amongst all other declarations or access candidates
* to a variable named `name` in the variable scope `scope`.
*/
private int rankVariableOrAccess(VariableScope scope, string name, DefOrAccessCand v) {
v = DenseRank2<DenseRankInput>::denseRank(scope, name, result + 1)
}
/**
* Holds if `v` can reach rank `rnk` in the variable scope `scope`. This is needed to
* take shadowing into account, for example in
*
* ```rust
* let x = 0; // rank 0
* use(x); // rank 1
* let x = ""; // rank 2
* use(x); // rank 3
* ```
*
* the declaration at rank 0 can only reach the access at rank 1, while the declaration
* at rank 2 can only reach the access at rank 3.
*/
private predicate variableReachesRank(
VariableScope scope, string name, NestedFunctionOrVariable v, int rnk
) {
rnk = rankVariableOrAccess(scope, name, v)
or
variableReachesRank(scope, name, v, rnk - 1) and
rnk = rankVariableOrAccess(scope, name, TDefOrAccessCandVariableAccessCand(_))
}
private predicate variableReachesCand(
VariableScope scope, string name, NestedFunctionOrVariable v, VariableAccessCand cand,
int nestLevel
) {
exists(int rnk |
variableReachesRank(scope, name, v, rnk) and
rnk = rankVariableOrAccess(scope, name, TDefOrAccessCandVariableAccessCand(cand)) and
variableAccessCandInScope(cand, scope, name, nestLevel, _)
)
}
pragma[nomagic]
predicate access(string name, NestedFunctionOrVariable v, VariableAccessCand cand) {
v =
min(NestedFunctionOrVariable v0, int nestLevel |
variableReachesCand(_, name, v0, cand, nestLevel)
|
v0 order by nestLevel
)
}
/** A variable access. */
class VariableAccess extends PathExprBaseImpl::PathExprBase {
private string name;
private Variable v;
VariableAccess() { variableAccess(name, v, this) }
/** Gets the variable being accessed. */
Variable getVariable() { result = v }
/** Holds if this access is a capture. */
predicate isCapture() { this.getEnclosingCfgScope() != v.getEnclosingCfgScope() }
override string toStringImpl() { result = name }
override string getAPrimaryQlClass() { result = "VariableAccess" }
}
/** Holds if `e` occurs in the LHS of an assignment or compound assignment. */
private predicate assignmentExprDescendant(Expr e) {
e = any(AssignmentExpr ae).getLhs()
or
exists(Expr mid |
assignmentExprDescendant(mid) and
getImmediateParent(e) = mid and
not mid instanceof DerefExpr and
not mid instanceof FieldExpr and
not mid instanceof IndexExpr
)
}
/** A variable write. */
class VariableWriteAccess extends VariableAccess {
cached
VariableWriteAccess() {
Cached::ref() and
assignmentExprDescendant(this)
}
}
/** A variable read. */
class VariableReadAccess extends VariableAccess {
cached
VariableReadAccess() {
Cached::ref() and
not this instanceof VariableWriteAccess and
not this = any(RefExpr re).getExpr() and
not this = any(CompoundAssignmentExpr cae).getLhs()
}
}
/** A nested function access. */
class NestedFunctionAccess extends PathExprBaseImpl::PathExprBase {
private Function f;
NestedFunctionAccess() { nestedFunctionAccess(_, f, this) }
/** Gets the function being accessed. */
Function getFunction() { result = f }
}
cached
private module Cached {
cached
predicate ref() { 1 = 1 }
cached
predicate backref() {
1 = 1
or
variableDecl(_, _, _)
or
exists(VariableReadAccess a)
or
exists(VariableWriteAccess a)
or
exists(any(Variable v).getParameter())
}
cached
newtype TVariable =
MkVariable(AstNode definingNode, string name) { variableDecl(definingNode, _, name) }
cached
predicate variableAccess(string name, Variable v, VariableAccessCand cand) {
access(name, TDefOrAccessCandVariable(v), cand)
}
cached
predicate nestedFunctionAccess(string name, Function f, VariableAccessCand cand) {
access(name, TDefOrAccessCandNestedFunction(f, _), cand)
}
}
private import Cached
}