Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rust: Various fixes to the CFG construction #17800

Merged
merged 18 commits into from
Oct 21, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
b97ec40
Rust: Add CFG tests with empty tuple and struct patterns
paldepind Oct 17, 2024
2d1c62b
Rust: Fix dead end in CFG for empty tuple and struct patterns
paldepind Oct 17, 2024
b0cd44e
Rust: Add CFG test cases
paldepind Oct 17, 2024
fd5d625
Rust: Avoid creating CFG scopes for trait signatures without implemen…
paldepind Oct 17, 2024
04f2062
Rust: Label the non-return CFG edge out of question mark as match
paldepind Oct 17, 2024
e6f1edc
Rust: Conditional completion of match arm expression should now flow …
paldepind Oct 17, 2024
b2032fc
Rust: Only normal completion of a let statement initializer steps to …
paldepind Oct 17, 2024
272d12f
Rust: Step correctly over method arguments
paldepind Oct 17, 2024
b0625f8
Rust: Add CFG test of range patterns
paldepind Oct 18, 2024
7aa28a0
Rust: Fix multiple CFG successors in range pattern without lower bound
paldepind Oct 18, 2024
6568eb8
Rust: Refactor CFG pattern tree implementation
paldepind Oct 18, 2024
4ca6b0e
Rust: Add a CFG test for a return within a break
paldepind Oct 18, 2024
28f111b
Rust: Remove erroneous CFG edge from return to break
paldepind Oct 18, 2024
b1e85d1
Rust: Refactor `BreakExprTree` to use `StandardPostOrderTree`
paldepind Oct 18, 2024
381f061
Rust: Add CFG test for match with no arms
paldepind Oct 21, 2024
9c172f6
Rust: Fix dead end in CFG for match expressions with no arms
paldepind Oct 21, 2024
3ae0475
Rust: Accept less CFG inconsistencies
paldepind Oct 21, 2024
a1ebf98
Merge branch 'main' into rust-cfg-fixes
paldepind Oct 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 3 additions & 7 deletions rust/ql/lib/codeql/rust/controlflow/internal/Completion.qll
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,7 @@ class SimpleCompletion extends NormalCompletion, TSimpleCompletion {

// `SimpleCompletion` is the "default" completion type, thus it is valid for
// any node where there isn't another more specific completion type.
override predicate isValidFor(AstNode e) {
not any(Completion c).isValidForSpecific(e)
or
// A `?` expression can both proceed normally or cause an early return, so
// we explicitly allow the former here.
e instanceof TryExpr
}
override predicate isValidFor(AstNode e) { not any(Completion c).isValidForSpecific(e) }

override string toString() { result = "simple" }
}
Expand Down Expand Up @@ -177,6 +171,8 @@ class MatchCompletion extends TMatchCompletion, ConditionalCompletion {
override predicate isValidForSpecific(AstNode e) {
e instanceof Pat and
if isExhaustiveMatch(e) then value = true else any()
or
e instanceof TryExpr and value = true
}

override MatchSuccessor getAMatchingSuccessorType() { result.getValue() = value }
Expand Down
111 changes: 35 additions & 76 deletions rust/ql/lib/codeql/rust/controlflow/internal/ControlFlowGraphImpl.qll
Original file line number Diff line number Diff line change
Expand Up @@ -147,23 +147,15 @@ class BlockExprTree extends StandardPostOrderTree, BlockExpr {
override predicate propagatesAbnormal(AstNode child) { child = this.getChildNode(_) }
}

class BreakExprTree extends PostOrderTree, BreakExpr {
override predicate propagatesAbnormal(AstNode child) { child = this.getExpr() }

override predicate first(AstNode node) {
first(this.getExpr(), node)
or
not this.hasExpr() and node = this
}
class BreakExprTree extends StandardPostOrderTree, BreakExpr {
override AstNode getChildNode(int i) { i = 0 and result = this.getExpr() }

override predicate last(AstNode last, Completion c) { none() }

override predicate succ(AstNode pred, AstNode succ, Completion c) {
last(super.getExpr(), pred, c) and succ = this
super.succ(pred, succ, c)
or
pred = this and
c.isValidFor(pred) and
succ = this.getTarget()
pred = this and c.isValidFor(pred) and succ = this.getTarget()
}
}

Expand Down Expand Up @@ -325,7 +317,7 @@ class LetStmtTree extends PreOrderTree, LetStmt {
not this.hasInitializer()
or
// Edge from end of initializer to pattern.
last(this.getInitializer(), pred, c) and first(this.getPat(), succ)
last(this.getInitializer(), pred, c) and first(this.getPat(), succ) and completionIsNormal(c)
or
// Edge from failed pattern to `else` branch.
last(this.getPat(), pred, c) and
Expand Down Expand Up @@ -507,9 +499,12 @@ class MatchExprTree extends PostOrderTree instanceof MatchExpr {
first(super.getArm(0).getPat(), succ) and
completionIsNormal(c)
or
// Edge from a failed match/guard in one arm to the beginning of the next arm.
// Edge from a failed pattern or guard in one arm to the beginning of the next arm.
exists(int i |
last(super.getArm(i), pred, c) and
(
last(super.getArm(i).getPat(), pred, c) or
last(super.getArm(i).getGuard().getCondition(), pred, c)
) and
first(super.getArm(i + 1), succ) and
c.(ConditionalCompletion).failed()
)
Expand All @@ -521,10 +516,7 @@ class MatchExprTree extends PostOrderTree instanceof MatchExpr {

class MethodCallExprTree extends StandardPostOrderTree, MethodCallExpr {
override AstNode getChildNode(int i) {
i = 0 and
result = this.getReceiver()
or
result = this.getArgList().getArg(i + 1)
if i = 0 then result = this.getReceiver() else result = this.getArgList().getArg(i - 1)
}
}

Expand Down Expand Up @@ -605,89 +597,48 @@ class YeetExprTree extends StandardPostOrderTree instanceof YeetExpr {
* `OrPat`s and `IdentPat`s.
*/
module PatternTrees {
abstract class PreOrderPatTree extends PreOrderTree {
abstract class StandardPatTree extends StandardTree {
abstract Pat getPat(int i);

private Pat getPatRanked(int i) {
Pat getPatRanked(int i) {
result = rank[i + 1](Pat pat, int j | pat = this.getPat(j) | pat order by j)
}

override predicate propagatesAbnormal(AstNode child) { child = this.getPatRanked(_) }
override AstNode getChildNode(int i) { result = this.getPat(i) }
}

abstract class PreOrderPatTree extends StandardPatTree, StandardPreOrderTree {
override predicate succ(AstNode pred, AstNode succ, Completion c) {
pred = this and
completionIsValidFor(c, this) and
c.(MatchCompletion).succeeded() and
first(this.getPatRanked(0), succ)
or
exists(int i | last(this.getPatRanked(i), pred, c) |
// Edge from successful pattern to the next
c.(MatchCompletion).succeeded() and
first(this.getPatRanked(i + 1), succ)
(
StandardPatTree.super.succ(pred, succ, c)
or
pred = this and first(this.getFirstChildNode(), succ) and completionIsValidFor(c, this)
)
}

override predicate last(AstNode node, Completion c) {
node = this and
completionIsValidFor(c, this) and
c.(MatchCompletion).failed()
super.last(node, c)
or
exists(int i | last(this.getPatRanked(i), node, c) |
c.(MatchCompletion).failed()
or
not exists(this.getPatRanked(i + 1)) and
completionIsNormal(c)
)
c.(MatchCompletion).failed() and
completionIsValidFor(c, this) and
(node = this or last(this.getPatRanked(_), node, c))
}
}

abstract class PostOrderPatTree extends PostOrderTree {
abstract Pat getPat(int i);

private Pat getPatRanked(int i) {
result = rank[i + 1](Pat pat, int j | pat = this.getPat(j) | pat order by j)
}

override predicate propagatesAbnormal(AstNode child) { child = this.getPatRanked(_) }

override predicate first(AstNode node) {
first(this.getPat(0), node)
or
not exists(this.getPat(_)) and
node = this
}

override predicate succ(AstNode pred, AstNode succ, Completion c) {
exists(int i | last(this.getPat(i), pred, c) |
// Edge from unsuccessful pattern to the next
c.(MatchCompletion).failed() and
first(this.getPat(i + 1), succ)
or
// Edge from successful pattern to this
c.(MatchCompletion).succeeded() and
succ = this
or
// Edge from last pattern to this
not exists(this.getPat(i + 1)) and
succ = this and
completionIsNormal(c)
)
}
}
abstract class PostOrderPatTree extends StandardPatTree, StandardPostOrderTree { }

class IdentPatTree extends PostOrderPatTree, IdentPat {
override Pat getPat(int i) { i = 0 and result = this.getPat() }

override predicate last(AstNode node, Completion c) {
super.last(node, c)
or
last(this.getPat(), node, c) and
c.(MatchCompletion).failed()
last(this.getPat(), node, c) and c.(MatchCompletion).failed()
}

override predicate succ(AstNode pred, AstNode succ, Completion c) {
super.succ(pred, succ, c) and
not (succ = this and c.(MatchCompletion).failed())
super.succ(pred, succ, c) and c.(MatchCompletion).succeeded()
}
}

Expand All @@ -705,6 +656,14 @@ module PatternTrees {

class OrPatTree extends PostOrderPatTree instanceof OrPat {
override Pat getPat(int i) { result = OrPat.super.getPat(i) }

override predicate succ(AstNode pred, AstNode succ, Completion c) {
// Failed patterns advance normally between children
c.(MatchCompletion).failed() and super.succ(pred, succ, c)
or
// Successful pattern step to this
c.(MatchCompletion).succeeded() and succ = this and last(this.getPat(_), pred, c)
}
}

class ParenPatTree extends ControlFlowTree, ParenPat {
Expand Down
6 changes: 6 additions & 0 deletions rust/ql/lib/codeql/rust/controlflow/internal/Scope.qll
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ abstract class CfgScope extends AstNode {
}

final class FunctionScope extends CfgScope, Function {
FunctionScope() {
// A function without a body corresponds to a trait method signature and
// should not have a CFG scope.
this.hasBody()
}

override predicate scopeFirst(AstNode node) {
first(this.(FunctionTree).getFirstChildNode(), node)
}
Expand Down
Loading
Loading