Skip to content

Commit 3da9403

Browse files
committed
Rust: Handle chained let expressions
1 parent 0e2b53c commit 3da9403

File tree

25 files changed

+351
-136
lines changed

25 files changed

+351
-136
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: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,8 +152,14 @@ module Impl {
152152
/** Gets the `let` statement that introduces this variable, if any. */
153153
LetStmt getLetStmt() { this.getPat() = result.getPat() }
154154

155+
/** Gets the `let` expression that introduces this variable, if any. */
156+
LetExpr getLetExpr() { this.getPat() = result.getPat() }
157+
155158
/** Gets the initial value of this variable, if any. */
156-
Expr getInitializer() { result = this.getLetStmt().getInitializer() }
159+
Expr getInitializer() {
160+
result = this.getLetStmt().getInitializer() or
161+
result = this.getLetExpr().getScrutinee()
162+
}
157163

158164
/** Holds if this variable is captured. */
159165
predicate isCaptured() { this.getAnAccess().isCapture() }
@@ -198,6 +204,7 @@ module Impl {
198204
n instanceof Pat or
199205
n instanceof VariableAccessCand or
200206
n instanceof LetStmt or
207+
n instanceof LetExpr or
201208
n instanceof VariableScope
202209
) and
203210
exists(AstNode n0 |
@@ -243,6 +250,7 @@ module Impl {
243250
this instanceof VariableScope or
244251
this instanceof VariableAccessCand or
245252
this instanceof LetStmt or
253+
this instanceof LetExpr or
246254
getImmediateChild(this, _) instanceof RelevantElement
247255
}
248256

@@ -354,6 +362,14 @@ module Impl {
354362
ord = getLastPreOrderNumbering(scope, let) + 1
355363
)
356364
or
365+
exists(LetExpr let |
366+
let.getPat() = pat and
367+
scope = getEnclosingScope(let) and
368+
// for `let` expressions, variables are bound _after_ the statement, i.e.
369+
// not in the RHS
370+
ord = getLastPreOrderNumbering(scope, let) + 1
371+
)
372+
or
357373
exists(IfExpr ie, LetExpr let |
358374
let.getPat() = pat and
359375
ie.getCondition() = let and

rust/ql/lib/codeql/rust/internal/TypeInference.qll

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,11 @@ private module CertainTypeInference {
328328
let.getInitializer() = n2
329329
)
330330
or
331+
exists(LetExpr let |
332+
let.getPat() = n1 and
333+
let.getScrutinee() = n2
334+
)
335+
or
331336
n1 = n2.(ParenExpr).getExpr()
332337
)
333338
or
@@ -466,11 +471,6 @@ private predicate typeEquality(AstNode n1, TypePath prefix1, AstNode n2, TypePat
466471
or
467472
n1 = n2.(MatchExpr).getAnArm().getExpr()
468473
or
469-
exists(LetExpr let |
470-
n1 = let.getScrutinee() and
471-
n2 = let.getPat()
472-
)
473-
or
474474
exists(MatchExpr me |
475475
n1 = me.getScrutinee() and
476476
n2 = me.getAnArm().getPat()

rust/ql/test/library-tests/controlflow-unstable/Cfg.expected

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,14 @@ edges
1313
| test.rs:5:67:11:5 | { ... } | test.rs:5:5:11:5 | exit fn test_and_if_let (normal) | |
1414
| test.rs:6:9:10:9 | if ... {...} else {...} | test.rs:5:67:11:5 | { ... } | |
1515
| test.rs:6:12:6:12 | a | test.rs:6:12:6:31 | [boolean(false)] ... && ... | false |
16-
| test.rs:6:12:6:12 | a | test.rs:6:17:6:31 | let ... = b | true |
16+
| test.rs:6:12:6:12 | a | test.rs:6:31:6:31 | b | true |
1717
| test.rs:6:12:6:31 | [boolean(false)] ... && ... | test.rs:9:13:9:17 | false | false |
1818
| test.rs:6:12:6:31 | [boolean(true)] ... && ... | test.rs:7:13:7:13 | d | true |
19-
| test.rs:6:17:6:31 | let ... = b | test.rs:6:31:6:31 | b | |
20-
| test.rs:6:21:6:27 | Some(...) | test.rs:6:12:6:31 | [boolean(false)] ... && ... | no-match |
19+
| test.rs:6:17:6:31 | [boolean(false)] let ... = b | test.rs:6:12:6:31 | [boolean(false)] ... && ... | false |
20+
| test.rs:6:17:6:31 | [boolean(true)] let ... = b | test.rs:6:12:6:31 | [boolean(true)] ... && ... | true |
21+
| test.rs:6:21:6:27 | Some(...) | test.rs:6:17:6:31 | [boolean(false)] let ... = b | no-match |
2122
| test.rs:6:21:6:27 | Some(...) | test.rs:6:26:6:26 | d | match |
22-
| test.rs:6:26:6:26 | d | test.rs:6:12:6:31 | [boolean(true)] ... && ... | match |
23+
| test.rs:6:26:6:26 | d | test.rs:6:17:6:31 | [boolean(true)] let ... = b | match |
2324
| test.rs:6:26:6:26 | d | test.rs:6:26:6:26 | d | |
2425
| test.rs:6:31:6:31 | b | test.rs:6:21:6:27 | Some(...) | |
2526
| test.rs:6:33:8:9 | { ... } | test.rs:6:9:10:9 | if ... {...} else {...} | |
@@ -40,13 +41,13 @@ edges
4041
| test.rs:13:59:21:5 | { ... } | test.rs:13:5:21:5 | exit fn test_and_if_let2 (normal) | |
4142
| test.rs:14:9:20:9 | if ... {...} else {...} | test.rs:13:59:21:5 | { ... } | |
4243
| test.rs:14:12:14:12 | a | test.rs:14:12:14:25 | [boolean(false)] ... && ... | false |
43-
| test.rs:14:12:14:12 | a | test.rs:14:17:14:25 | let ... = b | true |
44+
| test.rs:14:12:14:12 | a | test.rs:14:25:14:25 | b | true |
4445
| test.rs:14:12:14:25 | [boolean(false)] ... && ... | test.rs:14:12:15:16 | [boolean(false)] ... && ... | false |
4546
| test.rs:14:12:14:25 | [boolean(true)] ... && ... | test.rs:15:16:15:16 | c | true |
4647
| test.rs:14:12:15:16 | [boolean(false)] ... && ... | test.rs:19:13:19:17 | false | false |
4748
| test.rs:14:12:15:16 | [boolean(true)] ... && ... | test.rs:17:13:17:13 | d | true |
48-
| test.rs:14:17:14:25 | let ... = b | test.rs:14:25:14:25 | b | |
49-
| test.rs:14:21:14:21 | d | test.rs:14:12:14:25 | [boolean(true)] ... && ... | match |
49+
| test.rs:14:17:14:25 | [boolean(true)] let ... = b | test.rs:14:12:14:25 | [boolean(true)] ... && ... | true |
50+
| test.rs:14:21:14:21 | d | test.rs:14:17:14:25 | [boolean(true)] let ... = b | match |
5051
| test.rs:14:21:14:21 | d | test.rs:14:21:14:21 | d | |
5152
| test.rs:14:25:14:25 | b | test.rs:14:21:14:21 | d | |
5253
| test.rs:15:16:15:16 | c | test.rs:14:12:15:16 | [boolean(false)] ... && ... | false |

0 commit comments

Comments
 (0)