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: More CFG modelling #17633

Merged
merged 15 commits into from
Oct 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 12 additions & 0 deletions rust/ql/consistency-queries/CfgConsistency.ql
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import codeql.rust.controlflow.internal.Completion
query predicate nonPostOrderExpr(Expr e, string cls) {
cls = e.getPrimaryQlClasses() and
not e instanceof LetExpr and
not e instanceof ParenExpr and
not e instanceof LogicalAndExpr and // todo
not e instanceof LogicalOrExpr and
exists(AstNode last, Completion c |
Expand All @@ -25,3 +26,14 @@ query predicate scopeNoFirst(CfgScope scope) {
not scope = any(Function f | not exists(f.getBody())) and
not scope = any(ClosureExpr c | not exists(c.getBody()))
}

/** Holds if `be` is the `else` branch of a `let` statement that results in a panic. */
private predicate letElsePanic(BlockExpr be) {
be = any(LetStmt let).getLetElse().getBlockExpr() and
exists(Completion c | CfgImpl::last(be, _, c) | completionIsNormal(c))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks fine for now, we don't really have a way to identify "panic"-like statements. A real panic wouldn't complete normally, it would abort the program. When we extract types, we may refine this to hold if the type of the expression is !.

}

query predicate deadEnd(CfgImpl::Node node) {
Consistency::deadEnd(node) and
not letElsePanic(node.getAstNode())
}
6 changes: 5 additions & 1 deletion rust/ql/lib/codeql/rust/controlflow/internal/Completion.qll
Original file line number Diff line number Diff line change
Expand Up @@ -84,14 +84,18 @@ class BooleanCompletion extends ConditionalCompletion, TBooleanCompletion {
private predicate isValidForSpecific0(AstNode e) {
e = any(IfExpr c).getCondition()
or
any(MatchArm arm).getGuard() = e
e = any(WhileExpr c).getCondition()
or
any(MatchGuard guard).getCondition() = e
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we should drop /hideMatchGuard nodes from the AST at some point. They look like useless wrappers.

or
exists(BinaryExpr expr |
expr.getOperatorName() = ["&&", "||"] and
e = expr.getLhs()
)
or
exists(Expr parent | this.isValidForSpecific0(parent) |
e = parent.(ParenExpr).getExpr()
aibaars marked this conversation as resolved.
Show resolved Hide resolved
or
parent =
any(PrefixExpr expr |
expr.getOperatorName() = "!" and
Expand Down
159 changes: 142 additions & 17 deletions rust/ql/lib/codeql/rust/controlflow/internal/ControlFlowGraphImpl.qll
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ import CfgImpl
/** Holds if `p` is a trivial pattern that is always guaranteed to match. */
predicate trivialPat(Pat p) { p instanceof WildcardPat or p instanceof IdentPat }

class ArrayExprTree extends StandardPostOrderTree, ArrayExpr {
override AstNode getChildNode(int i) { result = this.getExpr(i) }
}

class AsmExprTree extends LeafTree instanceof AsmExpr { }

class AwaitExprTree extends StandardPostOrderTree instanceof AwaitExpr {
Expand Down Expand Up @@ -132,14 +136,40 @@ class LogicalAndBinaryOpExprTree extends PreOrderTree, LogicalAndExpr {
}
}

class BlockExprBaseTree extends StandardPostOrderTree instanceof BlockExpr {
class BlockExprTree extends StandardPostOrderTree, BlockExpr {
override AstNode getChildNode(int i) {
result = super.getStmtList().getStatement(i)
or
not exists(super.getStmtList().getStatement(i)) and
(exists(super.getStmtList().getStatement(i - 1)) or i = 0) and
result = super.getStmtList().getTailExpr()
}

override predicate propagatesAbnormal(AstNode child) { none() }

/** Holds if this block captures the break completion `c`. */
private predicate capturesBreakCompletion(LoopJumpCompletion c) {
c.isBreak() and
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you also handle shadowing of labels? It's a silly thing to do, and the rust compiler will warn about it, but shadowed labels are allowed. For example:

        'a: loop {
            'a: loop {
                'a: {
                    break 'a;
                }
                break 'a;
            }
            break 'a;
        }

You might want to add a test case, if you don't already have one;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, wait, I we don't have one that shadows; I'll add.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have added one now, and the CFG is correct:

flowchart TD
1["enter test_loop_label_shadowing"]
10["b"]
11["ContinueExpr"]
12["ExprStmt"]
13["ContinueExpr"]
14["ExprStmt"]
2["1"]
3["ExprStmt"]
4["ExprStmt"]
5["IfExpr"]
6["b"]
7["ContinueExpr"]
8["ExprStmt"]
9["IfExpr"]

1 --> 3
2 --> 4
3 --> 2
4 --> 6
5 --> 14
6 -- false --> 10
6 -- true --> 8
7 -- continue --> 4
8 --> 7
9 --> 5
10 -- false --> 9
10 -- true --> 12
11 -- continue('loop) --> 4
12 --> 11
13 -- continue('loop) --> 4
14 --> 13
Loading

c.getLabelName() = this.getLabel().getLifetime().getText()
}

override predicate succ(AstNode pred, AstNode succ, Completion c) {
super.succ(pred, succ, c)
or
// Edge for exiting the block with a break expressions
last(this.getChildNode(_), pred, c) and
this.capturesBreakCompletion(c) and
succ = this
}

override predicate last(AstNode last, Completion c) {
super.last(last, c)
or
// Any abnormal completions that this block does not capture should propagate
last(this.getChildNode(_), last, c) and
not completionIsNormal(c) and
not this.capturesBreakCompletion(c)
}
}

class BreakExprTree extends PostOrderTree instanceof BreakExpr {
Expand Down Expand Up @@ -189,7 +219,7 @@ class IfExprTree extends PostOrderTree instanceof IfExpr {
child = [super.getCondition(), super.getThen(), super.getElse()]
}

ConditionalCompletion conditionCompletion(Completion c) {
private ConditionalCompletion conditionCompletion(Completion c) {
if super.getCondition() instanceof LetExpr
then result = c.(MatchCompletion)
else result = c.(BooleanCompletion)
Expand Down Expand Up @@ -226,6 +256,8 @@ class IndexExprTree extends StandardPostOrderTree instanceof IndexExpr {
}
}

class ItemTree extends LeafTree, Item { }

// `LetExpr` is a pre-order tree such that the pattern itself ends up
// dominating successors in the graph in the same way that patterns do in
// `match` expressions.
Expand Down Expand Up @@ -277,45 +309,124 @@ class LetStmtTree extends PreOrderTree instanceof LetStmt {

class LiteralExprTree extends LeafTree instanceof LiteralExpr { }

class LoopExprTree extends PostOrderTree instanceof LoopExpr {
abstract class LoopingExprTree extends PostOrderTree {
override predicate propagatesAbnormal(AstNode child) { none() }

override predicate first(AstNode node) { first(super.getLoopBody(), node) }
abstract BlockExpr getLoopBody();

abstract Label getLabel();

/**
* Gets the node to execute when continuing the loop; either after
* executing the last node in the body or after an explicit `continue`.
*/
abstract AstNode getLoopContinue();

/** Whether this `LoopExpr` captures the `c` completion. */
/** Holds if this loop captures the `c` completion. */
private predicate capturesLoopJumpCompletion(LoopJumpCompletion c) {
not c.hasLabel()
or
c.getLabelName() = super.getLabel().getLifetime().getText()
c.getLabelName() = this.getLabel().getLifetime().getText()
}

override predicate succ(AstNode pred, AstNode succ, Completion c) {
// Edge for exiting the loop with a break expressions
last(this.getLoopBody(), pred, c) and
c.(LoopJumpCompletion).isBreak() and
this.capturesLoopJumpCompletion(c) and
succ = this
or
// Edge back to the start for final expression and continue expressions
last(super.getLoopBody(), pred, c) and
last(this.getLoopBody(), pred, c) and
(
completionIsNormal(c)
or
c.(LoopJumpCompletion).isContinue() and this.capturesLoopJumpCompletion(c)
) and
this.first(succ)
or
// Edge for exiting the loop with a break expressions
last(super.getLoopBody(), pred, c) and
c.(LoopJumpCompletion).isBreak() and
this.capturesLoopJumpCompletion(c) and
succ = this
first(this.getLoopContinue(), succ)
}

override predicate last(AstNode last, Completion c) {
super.last(last, c)
or
// Any abnormal completions that this loop does not capture should propagate
last(super.getLoopBody(), last, c) and
last(this.getLoopBody(), last, c) and
not completionIsNormal(c) and
not this.capturesLoopJumpCompletion(c)
}
}

class LoopExprTree extends LoopingExprTree instanceof LoopExpr {
override BlockExpr getLoopBody() { result = LoopExpr.super.getLoopBody() }

override Label getLabel() { result = LoopExpr.super.getLabel() }

override AstNode getLoopContinue() { result = this.getLoopBody() }

override predicate first(AstNode node) { first(this.getLoopBody(), node) }
}

class WhileExprTree extends LoopingExprTree instanceof WhileExpr {
override BlockExpr getLoopBody() { result = WhileExpr.super.getLoopBody() }

override Label getLabel() { result = WhileExpr.super.getLabel() }

override AstNode getLoopContinue() { result = super.getCondition() }

override predicate propagatesAbnormal(AstNode child) { child = super.getCondition() }

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

private ConditionalCompletion conditionCompletion(Completion c) {
if super.getCondition() instanceof LetExpr
then result = c.(MatchCompletion)
else result = c.(BooleanCompletion)
}

override predicate succ(AstNode pred, AstNode succ, Completion c) {
super.succ(pred, succ, c)
or
last(super.getCondition(), pred, c) and
this.conditionCompletion(c).succeeded() and
first(this.getLoopBody(), succ)
or
last(super.getCondition(), pred, c) and
this.conditionCompletion(c).failed() and
succ = this
}
}

class ForExprTree extends LoopingExprTree instanceof ForExpr {
override BlockExpr getLoopBody() { result = ForExpr.super.getLoopBody() }

override Label getLabel() { result = ForExpr.super.getLabel() }

override AstNode getLoopContinue() { result = super.getPat() }

override predicate propagatesAbnormal(AstNode child) { child = super.getIterable() }

override predicate first(AstNode node) { first(super.getIterable(), node) }

override predicate succ(AstNode pred, AstNode succ, Completion c) {
super.succ(pred, succ, c)
or
last(super.getIterable(), pred, c) and
first(super.getPat(), succ) and
completionIsNormal(c)
or
last(super.getPat(), pred, c) and
c.(MatchCompletion).succeeded() and
first(this.getLoopBody(), succ)
or
last(super.getPat(), pred, c) and
c.(MatchCompletion).failed() and
succ = this
Comment on lines +421 to +423
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks a little weird. The pattern in a for-loop can never fail, it is actually the implicit iter.next() call that may return None and exit the loop. Not sure if it matters much though. Perhaps we should have a special IteratorCompletion here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be even better if we could desugar for loops.

}
}

// TODO: replace with expanded macro once the extractor supports it
class MacroExprTree extends LeafTree, MacroExpr { }

class MatchArmTree extends ControlFlowTree instanceof MatchArm {
override predicate propagatesAbnormal(AstNode child) { child = super.getExpr() }

Expand Down Expand Up @@ -376,10 +487,24 @@ class MethodCallExprTree extends StandardPostOrderTree instanceof MethodCallExpr
}
}

class NameTree extends LeafTree, Name { }

class NameRefTree extends LeafTree, NameRef { }

class OffsetOfExprTree extends LeafTree instanceof OffsetOfExpr { }

class ParenExprTree extends StandardPostOrderTree, ParenExpr {
override AstNode getChildNode(int i) { i = 0 and result = super.getExpr() }
class ParenExprTree extends ControlFlowTree, ParenExpr {
private ControlFlowTree expr;

ParenExprTree() { expr = super.getExpr() }

override predicate propagatesAbnormal(AstNode child) { expr.propagatesAbnormal(child) }

override predicate first(AstNode first) { expr.first(first) }

override predicate last(AstNode last, Completion c) { expr.last(last, c) }

override predicate succ(AstNode pred, AstNode succ, Completion c) { none() }
}

// This covers all patterns as they all extend `Pat`
Expand Down

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

Loading
Loading