Skip to content

Commit f63e55c

Browse files
committed
Rust: Handle chained let expressions
1 parent fd1d940 commit f63e55c

File tree

28 files changed

+537
-199
lines changed

28 files changed

+537
-199
lines changed

rust/ql/lib/codeql/rust/controlflow/internal/CfgNodes.qll

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ private import codeql.rust.controlflow.CfgNodes
77
private import codeql.rust.internal.CachedStages
88

99
private predicate isPostOrder(AstNode n) {
10-
n instanceof Expr and
11-
not n instanceof LetExpr
10+
n instanceof Expr
1211
or
1312
n instanceof OrPat
1413
or

rust/ql/lib/codeql/rust/controlflow/internal/ControlFlowGraphImpl.qll

Lines changed: 7 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -200,8 +200,7 @@ class TypeReprTree extends LeafTree instanceof TypeRepr { }
200200
/**
201201
* Provides `ControlFlowTree`s for expressions.
202202
*
203-
* Since expressions construct values, they are modeled in post-order, except for
204-
* `LetExpr`s.
203+
* Since expressions construct values, they are modeled in post-order.
205204
*/
206205
module ExprTrees {
207206
class ArrayExprTree extends StandardPostOrderTree, ArrayExpr {
@@ -341,21 +340,15 @@ module ExprTrees {
341340
child = [super.getCondition(), super.getABranch()]
342341
}
343342

344-
private ConditionalCompletion conditionCompletion(Completion c) {
345-
if super.getCondition() instanceof LetExpr
346-
then result = c.(MatchCompletion)
347-
else result = c.(BooleanCompletion)
348-
}
349-
350343
override predicate succ(AstNode pred, AstNode succ, Completion c) {
351344
// Edges from the condition to the branches
352345
last(super.getCondition(), pred, c) and
353346
(
354-
first(super.getThen(), succ) and this.conditionCompletion(c).succeeded()
347+
first(super.getThen(), succ) and c.(ConditionalCompletion).succeeded()
355348
or
356-
first(super.getElse(), succ) and this.conditionCompletion(c).failed()
349+
first(super.getElse(), succ) and c.(ConditionalCompletion).failed()
357350
or
358-
not super.hasElse() and succ = this and this.conditionCompletion(c).failed()
351+
not super.hasElse() and succ = this and c.(ConditionalCompletion).failed()
359352
)
360353
or
361354
// An edge from the then branch to the last node
@@ -401,10 +394,7 @@ module ExprTrees {
401394
}
402395
}
403396

404-
// `LetExpr` is a pre-order tree such that the pattern itself ends up
405-
// dominating successors in the graph in the same way that patterns do in
406-
// `match` expressions.
407-
class LetExprTree extends StandardPreOrderTree, LetExpr {
397+
class LetExprTree extends StandardPostOrderTree, LetExpr {
408398
override AstNode getChildNode(int i) {
409399
i = 0 and
410400
result = this.getScrutinee()
@@ -456,21 +446,15 @@ module ExprTrees {
456446

457447
override predicate first(AstNode node) { first(super.getCondition(), node) }
458448

459-
private ConditionalCompletion conditionCompletion(Completion c) {
460-
if super.getCondition() instanceof LetExpr
461-
then result = c.(MatchCompletion)
462-
else result = c.(BooleanCompletion)
463-
}
464-
465449
override predicate succ(AstNode pred, AstNode succ, Completion c) {
466450
super.succ(pred, succ, c)
467451
or
468452
last(super.getCondition(), pred, c) and
469-
this.conditionCompletion(c).succeeded() and
453+
c.(ConditionalCompletion).succeeded() and
470454
first(this.getLoopBody(), succ)
471455
or
472456
last(super.getCondition(), pred, c) and
473-
this.conditionCompletion(c).failed() and
457+
c.(ConditionalCompletion).failed() and
474458
succ = this
475459
}
476460
}

rust/ql/lib/codeql/rust/controlflow/internal/Splitting.qll

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,7 @@ module ConditionalCompletionSplitting {
7171
child = parent.(LogicalNotExpr).getExpr() and
7272
childCompletion.getDual() = parentCompletion
7373
or
74-
(
75-
childCompletion = parentCompletion
76-
or
77-
// needed for `let` expressions
78-
childCompletion.(MatchCompletion).getValue() =
79-
parentCompletion.(BooleanCompletion).getValue()
80-
) and
74+
childCompletion = parentCompletion and
8175
(
8276
child = parent.(BinaryLogicalOperation).getAnOperand()
8377
or
@@ -92,6 +86,9 @@ module ConditionalCompletionSplitting {
9286
or
9387
child = parent.(PatternTrees::PostOrderPatTree).getPat(_)
9488
)
89+
or
90+
child = parent.(LetExpr).getPat() and
91+
childCompletion.(MatchCompletion).getValue() = parentCompletion.(BooleanCompletion).getValue()
9592
}
9693
}
9794

rust/ql/lib/codeql/rust/dataflow/Ssa.qll

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -194,9 +194,16 @@ module Ssa {
194194
ae.getRhs() = value
195195
)
196196
or
197-
exists(LetStmtCfgNode ls |
198-
ls.getPat().(IdentPatCfgNode).getName() = write and
199-
ls.getInitializer() = value
197+
exists(IdentPatCfgNode pat | pat.getName() = write |
198+
exists(LetStmtCfgNode ls |
199+
pat = ls.getPat() and
200+
ls.getInitializer() = value
201+
)
202+
or
203+
exists(LetExprCfgNode le |
204+
pat = le.getPat() and
205+
le.getScrutinee() = value
206+
)
200207
)
201208
}
202209

rust/ql/lib/codeql/rust/dataflow/internal/DataFlowImpl.qll

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,12 @@ module LocalFlow {
241241
nodeTo.getCfgNode() = s.getPat()
242242
)
243243
or
244+
// An edge from the right-hand side of a let expression to the left-hand side.
245+
exists(LetExprCfgNode e |
246+
nodeFrom.getCfgNode() = e.getScrutinee() and
247+
nodeTo.getCfgNode() = e.getPat()
248+
)
249+
or
244250
exists(IdentPatCfgNode p |
245251
not p.isRef() and
246252
nodeFrom.getCfgNode() = p and
@@ -379,6 +385,8 @@ module RustDataFlow implements InputSig<Location> {
379385
predicate neverSkipInPathGraph(Node node) {
380386
node.(Node::Node).getCfgNode() = any(LetStmtCfgNode s).getPat()
381387
or
388+
node.(Node::Node).getCfgNode() = any(LetExprCfgNode e).getPat()
389+
or
382390
node.(Node::Node).getCfgNode() = any(AssignmentExprCfgNode a).getLhs()
383391
or
384392
exists(MatchExprCfgNode match |
@@ -899,6 +907,12 @@ module VariableCapture {
899907
v.getPat() = ls.getPat().getPat() and
900908
ls.getInitializer() = source
901909
)
910+
or
911+
exists(LetExprCfgNode le |
912+
this = le and
913+
v.getPat() = le.getPat().getPat() and
914+
le.getScrutinee() = source
915+
)
902916
}
903917

904918
CapturedVariable getVariable() { result = v }

rust/ql/lib/codeql/rust/elements/internal/VariableImpl.qll

Lines changed: 99 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
private import rust
22
private import codeql.rust.controlflow.ControlFlowGraph
3-
private import codeql.rust.elements.internal.generated.ParentChild
3+
private import codeql.rust.elements.internal.generated.ParentChild as ParentChild
44
private import codeql.rust.elements.internal.PathImpl::Impl as PathImpl
55
private import codeql.rust.elements.internal.PathExprBaseImpl::Impl as PathExprBaseImpl
66
private import codeql.rust.elements.internal.FormatTemplateVariableAccessImpl::Impl as FormatTemplateVariableAccessImpl
@@ -36,6 +36,38 @@ module Impl {
3636
ClosureBodyScope() { this = any(ClosureExpr ce).getBody() }
3737
}
3838

39+
/**
40+
* A scope for conditions, which may introduce variables using `let` expressions.
41+
*
42+
* Such variables are only available in the body guarded by the condition.
43+
*/
44+
class ConditionScope extends VariableScope, Expr {
45+
private AstNode parent;
46+
private AstNode body;
47+
48+
ConditionScope() {
49+
parent =
50+
any(IfExpr ie |
51+
this = ie.getCondition() and
52+
body = ie.getThen()
53+
)
54+
or
55+
parent =
56+
any(WhileExpr we |
57+
this = we.getCondition() and
58+
body = we.getLoopBody()
59+
)
60+
}
61+
62+
/** Gets the parent of this condition. */
63+
AstNode getParent() { result = parent }
64+
65+
/**
66+
* Gets the body in which variables introduced in this scope are available.
67+
*/
68+
AstNode getBody() { result = body }
69+
}
70+
3971
private Pat getAPatAncestor(Pat p) {
4072
(p instanceof IdentPat or p instanceof OrPat) and
4173
exists(Pat p0 | result = p0.getParentPat() |
@@ -152,8 +184,14 @@ module Impl {
152184
/** Gets the `let` statement that introduces this variable, if any. */
153185
LetStmt getLetStmt() { this.getPat() = result.getPat() }
154186

187+
/** Gets the `let` expression that introduces this variable, if any. */
188+
LetExpr getLetExpr() { this.getPat() = result.getPat() }
189+
155190
/** Gets the initial value of this variable, if any. */
156-
Expr getInitializer() { result = this.getLetStmt().getInitializer() }
191+
Expr getInitializer() {
192+
result = this.getLetStmt().getInitializer() or
193+
result = this.getLetExpr().getScrutinee()
194+
}
157195

158196
/** Holds if this variable is captured. */
159197
predicate isCaptured() { this.getAnAccess().isCapture() }
@@ -193,15 +231,53 @@ module Impl {
193231
string getName() { result = name_ }
194232
}
195233

234+
pragma[nomagic]
235+
private Element getImmediateChildAdj(Element e, int preOrd, int index) {
236+
result = ParentChild::getImmediateChild(e, index) and
237+
preOrd = 0 and
238+
not exists(ConditionScope cs |
239+
e = cs.getParent() and
240+
result = cs.getBody()
241+
)
242+
or
243+
result = e.(ConditionScope).getBody() and
244+
preOrd = 1 and
245+
index = 0
246+
}
247+
248+
/**
249+
* An adjusted version of `ParentChild::getImmediateChild`, which makes the following
250+
* two adjustments:
251+
*
252+
* 1. For conditions like `if cond body`, instead of letting `body` be the second child
253+
* of `if`, we make it the last child of `cond`. This ensures that variables
254+
* introduced in the `cond` scope are available in `body`.
255+
*
256+
* 2. A similar adjustment is made for `while` loops: the body of the loop is made a
257+
* child of the loop condition instead of the loop itself.
258+
*/
259+
pragma[nomagic]
260+
private Element getImmediateChildAdj(Element e, int index) {
261+
result =
262+
rank[index + 1](Element res, int preOrd, int i |
263+
res = getImmediateChildAdj(e, preOrd, i)
264+
|
265+
res order by preOrd, i
266+
)
267+
}
268+
269+
private Element getImmediateParentAdj(Element e) { e = getImmediateChildAdj(result, _) }
270+
196271
private AstNode getAnAncestorInVariableScope(AstNode n) {
197272
(
198273
n instanceof Pat or
199274
n instanceof VariableAccessCand or
200275
n instanceof LetStmt or
276+
n = any(LetExpr le).getScrutinee() or
201277
n instanceof VariableScope
202278
) and
203279
exists(AstNode n0 |
204-
result = getImmediateParent(n0) or
280+
result = getImmediateParentAdj(n0) or
205281
result = n0.(FormatTemplateVariableAccess).getArgument().getParent().getParent()
206282
|
207283
n0 = n
@@ -243,31 +319,32 @@ module Impl {
243319
this instanceof VariableScope or
244320
this instanceof VariableAccessCand or
245321
this instanceof LetStmt or
246-
getImmediateChild(this, _) instanceof RelevantElement
322+
this = any(LetExpr le).getScrutinee() or
323+
getImmediateChildAdj(this, _) instanceof RelevantElement
247324
}
248325

249326
pragma[nomagic]
250-
private RelevantElement getChild(int index) { result = getImmediateChild(this, index) }
327+
private RelevantElement getChild(int index) { result = getImmediateChildAdj(this, index) }
251328

252329
pragma[nomagic]
253-
private RelevantElement getImmediateChildMin(int index) {
330+
private RelevantElement getImmediateChildAdjMin(int index) {
254331
// A child may have multiple positions for different accessors,
255332
// so always use the first
256333
result = this.getChild(index) and
257334
index = min(int i | result = this.getChild(i) | i)
258335
}
259336

260337
pragma[nomagic]
261-
RelevantElement getImmediateChild(int index) {
338+
RelevantElement getImmediateChildAdj(int index) {
262339
result =
263-
rank[index + 1](Element res, int i | res = this.getImmediateChildMin(i) | res order by i)
340+
rank[index + 1](Element res, int i | res = this.getImmediateChildAdjMin(i) | res order by i)
264341
}
265342

266343
pragma[nomagic]
267344
RelevantElement getImmediateLastChild() {
268345
exists(int last |
269-
result = this.getImmediateChild(last) and
270-
not exists(this.getImmediateChild(last + 1))
346+
result = this.getImmediateChildAdj(last) and
347+
not exists(this.getImmediateChildAdj(last + 1))
271348
)
272349
}
273350
}
@@ -288,13 +365,13 @@ module Impl {
288365
|
289366
// first child of a previously numbered node
290367
result = getPreOrderNumbering(scope, parent) + 1 and
291-
n = parent.getImmediateChild(0)
368+
n = parent.getImmediateChildAdj(0)
292369
or
293370
// non-first child of a previously numbered node
294371
exists(RelevantElement child, int i |
295372
result = getLastPreOrderNumbering(scope, child) + 1 and
296-
child = parent.getImmediateChild(i) and
297-
n = parent.getImmediateChild(i + 1)
373+
child = parent.getImmediateChildAdj(i) and
374+
n = parent.getImmediateChildAdj(i + 1)
298375
)
299376
)
300377
}
@@ -309,7 +386,7 @@ module Impl {
309386
result = getPreOrderNumbering(scope, leaf) and
310387
leaf != scope and
311388
(
312-
not exists(leaf.getImmediateChild(_))
389+
not exists(leaf.getImmediateChildAdj(_))
313390
or
314391
leaf instanceof VariableScope
315392
)
@@ -331,7 +408,7 @@ module Impl {
331408
/**
332409
* Holds if `v` is named `name` and is declared inside variable scope
333410
* `scope`. The pre-order numbering of the binding site of `v`, amongst
334-
* all nodes nester under `scope`, is `ord`.
411+
* all nodes nested under `scope`, is `ord`.
335412
*/
336413
private predicate variableDeclInScope(Variable v, VariableScope scope, string name, int ord) {
337414
name = v.getText() and
@@ -354,25 +431,20 @@ module Impl {
354431
ord = getLastPreOrderNumbering(scope, let) + 1
355432
)
356433
or
357-
exists(IfExpr ie, LetExpr let |
434+
exists(LetExpr let, Expr scrutinee |
358435
let.getPat() = pat and
359-
ie.getCondition() = let and
360-
scope = ie.getThen() and
361-
ord = getPreOrderNumbering(scope, scope)
436+
scrutinee = let.getScrutinee() and
437+
scope = getEnclosingScope(scrutinee) and
438+
// for `let` expressions, variables are bound _after_ the expression, i.e.
439+
// not in the RHS
440+
ord = getLastPreOrderNumbering(scope, scrutinee) + 1
362441
)
363442
or
364443
exists(ForExpr fe |
365444
fe.getPat() = pat and
366445
scope = fe.getLoopBody() and
367446
ord = getPreOrderNumbering(scope, scope)
368447
)
369-
or
370-
exists(WhileExpr we, LetExpr let |
371-
let.getPat() = pat and
372-
we.getCondition() = let and
373-
scope = we.getLoopBody() and
374-
ord = getPreOrderNumbering(scope, scope)
375-
)
376448
)
377449
)
378450
}
@@ -612,7 +684,7 @@ module Impl {
612684
or
613685
exists(Expr mid |
614686
assignmentExprDescendant(mid) and
615-
getImmediateParent(e) = mid and
687+
getImmediateParentAdj(e) = mid and
616688
not mid instanceof DerefExpr and
617689
not mid instanceof FieldExpr and
618690
not mid instanceof IndexExpr

0 commit comments

Comments
 (0)