Merge pull request #17415 from paldepind/rust-control-flow-graph

Rust: Basic control flow graph setup
This commit is contained in:
Simon Friis Vindum
2024-09-11 15:08:33 +02:00
committed by GitHub
11 changed files with 790 additions and 0 deletions

View File

@@ -0,0 +1,263 @@
private import rust
private import ControlFlowGraph
private import internal.SuccessorType
private import internal.ControlFlowGraphImpl as Impl
private import codeql.rust.generated.Raw
private import codeql.rust.generated.Synth
final class BasicBlock = BasicBlockImpl;
/**
* A basic block, that is, a maximal straight-line sequence of control flow nodes
* without branches or joins.
*/
private class BasicBlockImpl extends TBasicBlockStart {
/** Gets the scope of this basic block. */
CfgScope getScope() { result = this.getAPredecessor().getScope() }
/** Gets an immediate successor of this basic block, if any. */
BasicBlock getASuccessor() { result = this.getASuccessor(_) }
/** Gets an immediate successor of this basic block of a given type, if any. */
BasicBlock getASuccessor(SuccessorType t) {
result.getFirstNode() = this.getLastNode().getASuccessor(t)
}
/** Gets an immediate predecessor of this basic block, if any. */
BasicBlock getAPredecessor() { result.getASuccessor() = this }
/** Gets an immediate predecessor of this basic block of a given type, if any. */
BasicBlock getAPredecessor(SuccessorType t) { result.getASuccessor(t) = this }
/** Gets the control flow node at a specific (zero-indexed) position in this basic block. */
CfgNode getNode(int pos) { bbIndex(this.getFirstNode(), result, pos) }
/** Gets a control flow node in this basic block. */
CfgNode getANode() { result = this.getNode(_) }
/** Gets the first control flow node in this basic block. */
CfgNode getFirstNode() { this = TBasicBlockStart(result) }
/** Gets the last control flow node in this basic block. */
CfgNode getLastNode() { result = this.getNode(this.length() - 1) }
/** Gets the length of this basic block. */
int length() { result = strictcount(this.getANode()) }
predicate immediatelyDominates(BasicBlock bb) { bbIDominates(this, bb) }
predicate strictlyDominates(BasicBlock bb) { bbIDominates+(this, bb) }
predicate dominates(BasicBlock bb) {
bb = this or
this.strictlyDominates(bb)
}
predicate inDominanceFrontier(BasicBlock df) {
this.dominatesPredecessor(df) and
not this.strictlyDominates(df)
}
/**
* Holds if this basic block dominates a predecessor of `df`.
*/
private predicate dominatesPredecessor(BasicBlock df) { this.dominates(df.getAPredecessor()) }
BasicBlock getImmediateDominator() { bbIDominates(result, this) }
predicate strictlyPostDominates(BasicBlock bb) { bbIPostDominates+(this, bb) }
predicate postDominates(BasicBlock bb) {
this.strictlyPostDominates(bb) or
this = bb
}
/** Holds if this basic block is in a loop in the control flow graph. */
predicate inLoop() { this.getASuccessor+() = this }
/** Gets a textual representation of this basic block. */
string toString() { result = this.getFirstNode().toString() }
/** Gets the location of this basic block. */
Location getLocation() { result = this.getFirstNode().getLocation() }
}
cached
private module Cached {
/** Internal representation of basic blocks. */
cached
newtype TBasicBlock = TBasicBlockStart(CfgNode cfn) { startsBB(cfn) }
/** Holds if `cfn` starts a new basic block. */
private predicate startsBB(CfgNode cfn) {
not exists(cfn.getAPredecessor()) and exists(cfn.getASuccessor())
or
cfn.isJoin()
or
cfn.getAPredecessor().isBranch()
}
/**
* Holds if `succ` is a control flow successor of `pred` within
* the same basic block.
*/
private predicate intraBBSucc(CfgNode pred, CfgNode succ) {
succ = pred.getASuccessor() and
not startsBB(succ)
}
/**
* Holds if `cfn` is the `i`th node in basic block `bb`.
*
* In other words, `i` is the shortest distance from a node `bb`
* that starts a basic block to `cfn` along the `intraBBSucc` relation.
*/
cached
predicate bbIndex(CfgNode bbStart, CfgNode cfn, int i) =
shortestDistances(startsBB/1, intraBBSucc/2)(bbStart, cfn, i)
/**
* Holds if the first node of basic block `succ` is a control flow
* successor of the last node of basic block `pred`.
*/
private predicate succBB(BasicBlock pred, BasicBlock succ) { succ = pred.getASuccessor() }
/** Holds if `dom` is an immediate dominator of `bb`. */
cached
predicate bbIDominates(BasicBlock dom, BasicBlock bb) =
idominance(entryBB/1, succBB/2)(_, dom, bb)
/** Holds if `pred` is a basic block predecessor of `succ`. */
private predicate predBB(BasicBlock succ, BasicBlock pred) { succBB(pred, succ) }
/** Holds if `bb` is an exit basic block that represents normal exit. */
private predicate normalExitBB(BasicBlock bb) {
bb.getANode().(Impl::AnnotatedExitNode).isNormal()
}
/** Holds if `dom` is an immediate post-dominator of `bb`. */
cached
predicate bbIPostDominates(BasicBlock dom, BasicBlock bb) =
idominance(normalExitBB/1, predBB/2)(_, dom, bb)
/**
* Gets the `i`th predecessor of join block `jb`, with respect to some
* arbitrary order.
*/
cached
JoinBlockPredecessor getJoinBlockPredecessor(JoinBlock jb, int i) {
result =
rank[i + 1](JoinBlockPredecessor jbp |
jbp = jb.getAPredecessor()
|
jbp order by JoinBlockPredecessors::getId(jbp), JoinBlockPredecessors::getSplitString(jbp)
)
}
}
private import Cached
/** Holds if `bb` is an entry basic block. */
private predicate entryBB(BasicBlock bb) { bb.getFirstNode() instanceof Impl::EntryNode }
/**
* An entry basic block, that is, a basic block whose first node is
* an entry node.
*/
class EntryBasicBlock extends BasicBlockImpl {
EntryBasicBlock() { entryBB(this) }
override CfgScope getScope() {
this.getFirstNode() = any(Impl::EntryNode node | node.getScope() = result)
}
}
/**
* An annotated exit basic block, that is, a basic block whose last node is
* an annotated exit node.
*/
class AnnotatedExitBasicBlock extends BasicBlockImpl {
private boolean normal;
AnnotatedExitBasicBlock() {
exists(Impl::AnnotatedExitNode n |
n = this.getANode() and
if n.isNormal() then normal = true else normal = false
)
}
/** Holds if this block represent a normal exit. */
final predicate isNormal() { normal = true }
}
/**
* An exit basic block, that is, a basic block whose last node is
* an exit node.
*/
class ExitBasicBlock extends BasicBlockImpl {
ExitBasicBlock() { this.getLastNode() instanceof Impl::ExitNode }
}
private module JoinBlockPredecessors {
private predicate id(Raw::AstNode x, Raw::AstNode y) { x = y }
private predicate idOfDbAstNode(Raw::AstNode x, int y) = equivalenceRelation(id/2)(x, y)
// TODO: does not work if fresh ipa entities (`ipa: on:`) turn out to be first of the block
private predicate idOf(AstNode x, int y) { idOfDbAstNode(Synth::convertAstNodeToRaw(x), y) }
int getId(JoinBlockPredecessor jbp) {
idOf(jbp.getFirstNode().(Impl::AstCfgNode).getAstNode(), result)
or
idOf(jbp.(EntryBasicBlock).getScope(), result)
}
string getSplitString(JoinBlockPredecessor jbp) {
result = jbp.getFirstNode().(Impl::AstCfgNode).getSplitsString()
or
not exists(jbp.getFirstNode().(Impl::AstCfgNode).getSplitsString()) and
result = ""
}
}
/** A basic block with more than one predecessor. */
class JoinBlock extends BasicBlockImpl {
JoinBlock() { this.getFirstNode().isJoin() }
/**
* Gets the `i`th predecessor of this join block, with respect to some
* arbitrary order.
*/
JoinBlockPredecessor getJoinBlockPredecessor(int i) { result = getJoinBlockPredecessor(this, i) }
}
/** A basic block that is an immediate predecessor of a join block. */
class JoinBlockPredecessor extends BasicBlockImpl {
JoinBlockPredecessor() { this.getASuccessor() instanceof JoinBlock }
}
/** A basic block that terminates in a condition, splitting the subsequent control flow. */
class ConditionBlock extends BasicBlockImpl {
ConditionBlock() { this.getLastNode().isCondition() }
/**
* Holds if basic block `succ` is immediately controlled by this basic
* block with conditional value `s`. That is, `succ` is an immediate
* successor of this block, and `succ` can only be reached from
* the callable entry point by going via the `s` edge out of this basic block.
*/
pragma[nomagic]
predicate immediatelyControls(BasicBlock succ, SuccessorType s) {
succ = this.getASuccessor(s) and
forall(BasicBlock pred | pred = succ.getAPredecessor() and pred != this | succ.dominates(pred))
}
/**
* Holds if basic block `controlled` is controlled by this basic block with
* conditional value `s`. That is, `controlled` can only be reached from
* the callable entry point by going via the `s` edge out of this basic block.
*/
predicate controls(BasicBlock controlled, BooleanSuccessor s) {
exists(BasicBlock succ | this.immediatelyControls(succ, s) | succ.dominates(controlled))
}
}

View File

@@ -0,0 +1,38 @@
/** Provides classes representing the control flow graph. */
private import rust
private import internal.ControlFlowGraphImpl
private import internal.Completion
private import internal.SuccessorType
private import internal.Scope as Scope
private import BasicBlocks
final class CfgScope = Scope::CfgScope;
/**
* A control flow node.
*
* A control flow node is a node in the control flow graph (CFG). There is a
* many-to-one relationship between CFG nodes and AST nodes.
*
* Only nodes that can be reached from an entry point are included in the CFG.
*/
final class CfgNode extends Node {
/** Gets the file of this control flow node. */
File getFile() { result = this.getLocation().getFile() }
/** Gets a successor node of a given type, if any. */
CfgNode getASuccessor(SuccessorType t) { result = super.getASuccessor(t) }
/** Gets an immediate successor, if any. */
CfgNode getASuccessor() { result = this.getASuccessor(_) }
/** Gets an immediate predecessor node of a given flow type, if any. */
CfgNode getAPredecessor(SuccessorType t) { result.getASuccessor(t) = this }
/** Gets an immediate predecessor, if any. */
CfgNode getAPredecessor() { result = this.getAPredecessor(_) }
/** Gets the basic block that this control flow node belongs to. */
BasicBlock getBasicBlock() { result.getANode() = this }
}

View File

@@ -0,0 +1,93 @@
private import codeql.util.Boolean
private import codeql.rust.controlflow.ControlFlowGraph
private import rust
private import SuccessorType
private newtype TCompletion =
TSimpleCompletion() or
TBooleanCompletion(Boolean b) or
TReturnCompletion()
/** A completion of a statement or an expression. */
abstract class Completion extends TCompletion {
/** Gets a textual representation of this completion. */
abstract string toString();
predicate isValidForSpecific(AstNode e) { none() }
predicate isValidFor(AstNode e) { this.isValidForSpecific(e) }
/** Gets a successor type that matches this completion. */
abstract SuccessorType getAMatchingSuccessorType();
}
/**
* A completion that represents normal evaluation of a statement or an
* expression.
*/
abstract class NormalCompletion extends Completion { }
/** A simple (normal) completion. */
class SimpleCompletion extends NormalCompletion, TSimpleCompletion {
override NormalSuccessor getAMatchingSuccessorType() { any() }
// `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) }
override string toString() { result = "simple" }
}
/**
* A completion that represents evaluation of an expression, whose value
* determines the successor.
*/
abstract class ConditionalCompletion extends NormalCompletion {
boolean value;
bindingset[value]
ConditionalCompletion() { any() }
/** Gets the Boolean value of this conditional completion. */
final boolean getValue() { result = value }
/** Gets the dual completion. */
abstract ConditionalCompletion getDual();
}
/**
* A completion that represents evaluation of an expression
* with a Boolean value.
*/
class BooleanCompletion extends ConditionalCompletion, TBooleanCompletion {
BooleanCompletion() { this = TBooleanCompletion(value) }
override predicate isValidForSpecific(AstNode e) { e = any(IfExpr c).getCondition() }
/** Gets the dual Boolean completion. */
override BooleanCompletion getDual() { result = TBooleanCompletion(value.booleanNot()) }
override BooleanSuccessor getAMatchingSuccessorType() { result.getValue() = value }
override string toString() { result = "boolean(" + value + ")" }
}
/**
* A completion that represents a return.
*/
class ReturnCompletion extends TReturnCompletion, Completion {
override ReturnSuccessor getAMatchingSuccessorType() { any() }
override predicate isValidForSpecific(AstNode e) { e instanceof ReturnExpr }
override string toString() { result = "return" }
}
/** Hold if `c` represents normal evaluation of a statement or an expression. */
predicate completionIsNormal(Completion c) { c instanceof NormalCompletion }
/** Hold if `c` represents simple and normal evaluation of a statement or an expression. */
predicate completionIsSimple(Completion c) { c instanceof SimpleCompletion }
/** Holds if `c` is a valid completion for `n`. */
predicate completionIsValidFor(Completion c, AstNode n) { c.isValidFor(n) }

View File

@@ -0,0 +1,129 @@
private import rust
import codeql.controlflow.Cfg
import Completion
import codeql.controlflow.Cfg
private import SuccessorType as ST
private import Scope as Scope
module CfgInput implements InputSig<Location> {
private import rust as Rust
private import Completion as C
private import Splitting as S
class AstNode = Rust::AstNode;
class Completion = C::Completion;
predicate completionIsNormal = C::completionIsNormal/1;
predicate completionIsSimple = C::completionIsSimple/1;
predicate completionIsValidFor = C::completionIsValidFor/2;
/** An AST node with an associated control-flow graph. */
class CfgScope = Scope::CfgScope;
CfgScope getCfgScope(AstNode n) { result = Scope::scopeOfAst(n) }
class SplitKindBase = S::TSplitKind;
class Split = S::Split;
class SuccessorType = ST::SuccessorType;
/** Gets a successor type that matches completion `c`. */
SuccessorType getAMatchingSuccessorType(Completion c) { result = c.getAMatchingSuccessorType() }
/**
* Hold if `c` represents simple (normal) evaluation of a statement or an expression.
*/
predicate successorTypeIsSimple(SuccessorType t) { t instanceof ST::NormalSuccessor }
/** Holds if `t` is an abnormal exit type out of a CFG scope. */
predicate isAbnormalExitType(SuccessorType t) { none() }
/** Hold if `t` represents a conditional successor type. */
predicate successorTypeIsCondition(SuccessorType t) { t instanceof ST::BooleanSuccessor }
/** Gets the maximum number of splits allowed for a given node. */
int maxSplits() { result = 0 }
/** Holds if `first` is first executed when entering `scope`. */
predicate scopeFirst(CfgScope scope, AstNode first) {
scope.(CfgImpl::ControlFlowTree).first(first)
}
/** Holds if `scope` is exited when `last` finishes with completion `c`. */
predicate scopeLast(CfgScope scope, AstNode last, Completion c) {
scope.(CfgImpl::ControlFlowTree).last(last, c)
}
}
module CfgImpl = Make<Location, CfgInput>;
import CfgImpl
class FunctionTree extends StandardPostOrderTree instanceof Function {
override ControlFlowTree getChildNode(int i) { i = 0 and result = super.getBody() }
}
class BlockExprTree extends StandardPostOrderTree instanceof BlockExpr {
override ControlFlowTree getChildNode(int i) {
result = super.getStatement(i)
or
not exists(super.getStatement(i)) and
(exists(super.getStatement(i - 1)) or i = 0) and
result = super.getTail()
}
}
class CallExprTree extends StandardPostOrderTree instanceof CallExpr {
override ControlFlowTree getChildNode(int i) { result = super.getArg(i) }
}
class BinaryOpExprTree extends StandardPostOrderTree instanceof BinaryOpExpr {
override ControlFlowTree getChildNode(int i) {
i = 0 and result = super.getLhs()
or
i = 1 and result = super.getRhs()
}
}
class IfExprTree extends PostOrderTree instanceof IfExpr {
override predicate first(AstNode node) { first(super.getCondition(), node) }
override predicate propagatesAbnormal(AstNode child) {
child = [super.getCondition(), super.getThen(), super.getElse()]
}
override predicate succ(AstNode pred, AstNode succ, Completion c) {
// Edges from the condition to each branch
last(super.getCondition(), pred, c) and
(
first(super.getThen(), succ) and c.(BooleanCompletion).getValue() = true
or
first(super.getElse(), succ) and c.(BooleanCompletion).getValue() = false
)
or
// An edge from the then branch to the last node
last(super.getThen(), pred, c) and
succ = this and
completionIsNormal(c)
or
// An edge from the else branch to the last node
last(super.getElse(), pred, c) and
succ = this and
completionIsNormal(c)
}
}
class LetExprTree extends StandardPostOrderTree instanceof LetExpr {
override ControlFlowTree getChildNode(int i) { i = 0 and result = super.getExpr() }
}
class LiteralExprTree extends LeafTree instanceof LiteralExpr { }
class PathExprTree extends LeafTree instanceof PathExpr { }
// A leaf tree for unimplemented nodes in the AST.
class UnimplementedTree extends LeafTree instanceof Unimplemented { }

View File

@@ -0,0 +1,50 @@
/**
* @name Print CFG
* @description Produces a representation of a file's Control Flow Graph.
* This query is used by the VS Code extension.
* @id rust/print-cfg
* @kind graph
* @tags ide-contextual-queries/print-cfg
*/
private import codeql.rust.elements.File
private import codeql.rust.controlflow.internal.ControlFlowGraphImpl
private import codeql.rust.controlflow.ControlFlowGraph
/**
* Gets the source file to generate a CFG from.
*/
external string selectedSourceFile();
private predicate selectedSourceFileAlias = selectedSourceFile/0;
/**
* Gets the source line to generate a CFG from.
*/
external int selectedSourceLine();
private predicate selectedSourceLineAlias = selectedSourceLine/0;
/**
* Gets the source column to generate a CFG from.
*/
external int selectedSourceColumn();
private predicate selectedSourceColumnAlias = selectedSourceColumn/0;
private module ViewCfgQueryInput implements ViewCfgQueryInputSig<File> {
predicate selectedSourceFile = selectedSourceFileAlias/0;
predicate selectedSourceLine = selectedSourceLineAlias/0;
predicate selectedSourceColumn = selectedSourceColumnAlias/0;
predicate cfgScopeSpan(
CfgScope scope, File file, int startLine, int startColumn, int endLine, int endColumn
) {
file = scope.getFile() and
scope.getLocation().hasLocationInfo(_, startLine, startColumn, endLine, endColumn)
}
}
import ViewCfgQuery<File, ViewCfgQueryInput>

View File

@@ -0,0 +1,32 @@
private import rust
private import Completion
private import codeql.rust.generated.ParentChild
abstract class CfgScope extends AstNode { }
class FunctionScope extends CfgScope, Function { }
/**
* Gets the immediate parent of a non-`AstNode` element `e`.
*
* We restrict `e` to be a non-`AstNode` to skip past non-`AstNode` in
* the transitive closure computation in `getParentOfAst`. This is
* necessary because the parent of an `AstNode` is not necessarily an `AstNode`.
*/
private Element getParentOfAstStep(Element e) {
not e instanceof AstNode and
result = getImmediateParent(e)
}
/** Gets the nearest enclosing parent of `ast` that is an `AstNode`. */
private AstNode getParentOfAst(AstNode ast) {
result = getParentOfAstStep*(getImmediateParent(ast))
}
/** Gets the enclosing scope of a node */
cached
AstNode scopeOfAst(AstNode n) {
exists(AstNode p | p = getParentOfAst(n) |
if p instanceof CfgScope then p = result else result = scopeOfAst(p)
)
}

View File

@@ -0,0 +1,17 @@
cached
private module Cached {
// Not using CFG splitting, so the following are just placeholder types.
cached
newtype TSplitKind = TSplitKindUnit()
cached
newtype TSplit = TSplitUnit()
}
import Cached
/** A split for a control flow node. */
class Split extends TSplit {
/** Gets a textual representation of this split. */
string toString() { none() }
}

View File

@@ -0,0 +1,45 @@
private import codeql.util.Boolean
cached
newtype TSuccessorType =
TSuccessorSuccessor() or
TBooleanSuccessor(Boolean b) or
TReturnSuccessor()
/** The type of a control flow successor. */
abstract private class SuccessorTypeImpl extends TSuccessorType {
/** Gets a textual representation of successor type. */
abstract string toString();
}
final class SuccessorType = SuccessorTypeImpl;
/** A normal control flow successor. */
final class NormalSuccessor extends SuccessorTypeImpl, TSuccessorSuccessor {
final override string toString() { result = "successor" }
}
/** A conditional control flow successor. */
abstract private class ConditionalSuccessor extends SuccessorTypeImpl {
boolean value;
bindingset[value]
ConditionalSuccessor() { any() }
/** Gets the Boolean value of this successor. */
final boolean getValue() { result = value }
override string toString() { result = this.getValue().toString() }
}
/** A Boolean control flow successor. */
final class BooleanSuccessor extends ConditionalSuccessor, TBooleanSuccessor {
BooleanSuccessor() { this = TBooleanSuccessor(value) }
}
/**
* A `return` control flow successor.
*/
final class ReturnSuccessor extends SuccessorTypeImpl, TReturnSuccessor {
final override string toString() { result = "return" }
}

View File

@@ -0,0 +1,92 @@
nodes
| test.rs:1:1:7:1 | enter main | semmle.order | 1 |
| test.rs:1:1:7:1 | exit main | semmle.order | 2 |
| test.rs:1:1:7:1 | exit main (normal) | semmle.order | 3 |
| test.rs:1:1:7:1 | main | semmle.order | 4 |
| test.rs:1:18:7:1 | BlockExpr | semmle.order | 5 |
| test.rs:2:5:6:5 | IfExpr | semmle.order | 6 |
| test.rs:2:8:2:12 | LiteralExpr | semmle.order | 7 |
| test.rs:2:8:2:21 | BinaryOpExpr | semmle.order | 8 |
| test.rs:2:17:2:21 | LiteralExpr | semmle.order | 9 |
| test.rs:2:23:4:5 | BlockExpr | semmle.order | 10 |
| test.rs:3:9:3:20 | CallExpr | semmle.order | 11 |
| test.rs:3:19:3:19 | LiteralExpr | semmle.order | 12 |
| test.rs:4:12:6:5 | BlockExpr | semmle.order | 13 |
| test.rs:5:9:5:20 | CallExpr | semmle.order | 14 |
| test.rs:5:19:5:19 | LiteralExpr | semmle.order | 15 |
| test.rs:9:1:16:1 | decrement | semmle.order | 16 |
| test.rs:9:1:16:1 | enter decrement | semmle.order | 17 |
| test.rs:9:1:16:1 | exit decrement | semmle.order | 18 |
| test.rs:9:1:16:1 | exit decrement (normal) | semmle.order | 19 |
| test.rs:9:29:16:1 | BlockExpr | semmle.order | 20 |
| test.rs:11:5:15:5 | IfExpr | semmle.order | 21 |
| test.rs:11:8:11:8 | PathExpr | semmle.order | 22 |
| test.rs:11:8:11:13 | BinaryOpExpr | semmle.order | 23 |
| test.rs:11:13:11:13 | LiteralExpr | semmle.order | 24 |
| test.rs:11:15:13:5 | BlockExpr | semmle.order | 25 |
| test.rs:12:9:12:9 | LiteralExpr | semmle.order | 26 |
| test.rs:13:12:15:5 | BlockExpr | semmle.order | 27 |
| test.rs:14:9:14:9 | PathExpr | semmle.order | 28 |
| test.rs:14:9:14:13 | BinaryOpExpr | semmle.order | 29 |
| test.rs:14:13:14:13 | LiteralExpr | semmle.order | 30 |
edges
| test.rs:1:1:7:1 | enter main | test.rs:2:8:2:12 | LiteralExpr | semmle.label | |
| test.rs:1:1:7:1 | enter main | test.rs:2:8:2:12 | LiteralExpr | semmle.order | 1 |
| test.rs:1:1:7:1 | exit main (normal) | test.rs:1:1:7:1 | exit main | semmle.label | |
| test.rs:1:1:7:1 | exit main (normal) | test.rs:1:1:7:1 | exit main | semmle.order | 1 |
| test.rs:1:1:7:1 | main | test.rs:1:1:7:1 | exit main (normal) | semmle.label | |
| test.rs:1:1:7:1 | main | test.rs:1:1:7:1 | exit main (normal) | semmle.order | 1 |
| test.rs:1:18:7:1 | BlockExpr | test.rs:1:1:7:1 | main | semmle.label | |
| test.rs:1:18:7:1 | BlockExpr | test.rs:1:1:7:1 | main | semmle.order | 1 |
| test.rs:2:5:6:5 | IfExpr | test.rs:1:18:7:1 | BlockExpr | semmle.label | |
| test.rs:2:5:6:5 | IfExpr | test.rs:1:18:7:1 | BlockExpr | semmle.order | 1 |
| test.rs:2:8:2:12 | LiteralExpr | test.rs:2:17:2:21 | LiteralExpr | semmle.label | |
| test.rs:2:8:2:12 | LiteralExpr | test.rs:2:17:2:21 | LiteralExpr | semmle.order | 1 |
| test.rs:2:8:2:21 | BinaryOpExpr | test.rs:3:19:3:19 | LiteralExpr | semmle.label | true |
| test.rs:2:8:2:21 | BinaryOpExpr | test.rs:3:19:3:19 | LiteralExpr | semmle.order | 1 |
| test.rs:2:8:2:21 | BinaryOpExpr | test.rs:5:19:5:19 | LiteralExpr | semmle.label | false |
| test.rs:2:8:2:21 | BinaryOpExpr | test.rs:5:19:5:19 | LiteralExpr | semmle.order | 2 |
| test.rs:2:17:2:21 | LiteralExpr | test.rs:2:8:2:21 | BinaryOpExpr | semmle.label | |
| test.rs:2:17:2:21 | LiteralExpr | test.rs:2:8:2:21 | BinaryOpExpr | semmle.order | 1 |
| test.rs:2:23:4:5 | BlockExpr | test.rs:2:5:6:5 | IfExpr | semmle.label | |
| test.rs:2:23:4:5 | BlockExpr | test.rs:2:5:6:5 | IfExpr | semmle.order | 1 |
| test.rs:3:9:3:20 | CallExpr | test.rs:2:23:4:5 | BlockExpr | semmle.label | |
| test.rs:3:9:3:20 | CallExpr | test.rs:2:23:4:5 | BlockExpr | semmle.order | 1 |
| test.rs:3:19:3:19 | LiteralExpr | test.rs:3:9:3:20 | CallExpr | semmle.label | |
| test.rs:3:19:3:19 | LiteralExpr | test.rs:3:9:3:20 | CallExpr | semmle.order | 1 |
| test.rs:4:12:6:5 | BlockExpr | test.rs:2:5:6:5 | IfExpr | semmle.label | |
| test.rs:4:12:6:5 | BlockExpr | test.rs:2:5:6:5 | IfExpr | semmle.order | 1 |
| test.rs:5:9:5:20 | CallExpr | test.rs:4:12:6:5 | BlockExpr | semmle.label | |
| test.rs:5:9:5:20 | CallExpr | test.rs:4:12:6:5 | BlockExpr | semmle.order | 1 |
| test.rs:5:19:5:19 | LiteralExpr | test.rs:5:9:5:20 | CallExpr | semmle.label | |
| test.rs:5:19:5:19 | LiteralExpr | test.rs:5:9:5:20 | CallExpr | semmle.order | 1 |
| test.rs:9:1:16:1 | decrement | test.rs:9:1:16:1 | exit decrement (normal) | semmle.label | |
| test.rs:9:1:16:1 | decrement | test.rs:9:1:16:1 | exit decrement (normal) | semmle.order | 1 |
| test.rs:9:1:16:1 | enter decrement | test.rs:11:8:11:8 | PathExpr | semmle.label | |
| test.rs:9:1:16:1 | enter decrement | test.rs:11:8:11:8 | PathExpr | semmle.order | 1 |
| test.rs:9:1:16:1 | exit decrement (normal) | test.rs:9:1:16:1 | exit decrement | semmle.label | |
| test.rs:9:1:16:1 | exit decrement (normal) | test.rs:9:1:16:1 | exit decrement | semmle.order | 1 |
| test.rs:9:29:16:1 | BlockExpr | test.rs:9:1:16:1 | decrement | semmle.label | |
| test.rs:9:29:16:1 | BlockExpr | test.rs:9:1:16:1 | decrement | semmle.order | 1 |
| test.rs:11:5:15:5 | IfExpr | test.rs:9:29:16:1 | BlockExpr | semmle.label | |
| test.rs:11:5:15:5 | IfExpr | test.rs:9:29:16:1 | BlockExpr | semmle.order | 1 |
| test.rs:11:8:11:8 | PathExpr | test.rs:11:13:11:13 | LiteralExpr | semmle.label | |
| test.rs:11:8:11:8 | PathExpr | test.rs:11:13:11:13 | LiteralExpr | semmle.order | 1 |
| test.rs:11:8:11:13 | BinaryOpExpr | test.rs:12:9:12:9 | LiteralExpr | semmle.label | true |
| test.rs:11:8:11:13 | BinaryOpExpr | test.rs:12:9:12:9 | LiteralExpr | semmle.order | 1 |
| test.rs:11:8:11:13 | BinaryOpExpr | test.rs:14:9:14:9 | PathExpr | semmle.label | false |
| test.rs:11:8:11:13 | BinaryOpExpr | test.rs:14:9:14:9 | PathExpr | semmle.order | 2 |
| test.rs:11:13:11:13 | LiteralExpr | test.rs:11:8:11:13 | BinaryOpExpr | semmle.label | |
| test.rs:11:13:11:13 | LiteralExpr | test.rs:11:8:11:13 | BinaryOpExpr | semmle.order | 1 |
| test.rs:11:15:13:5 | BlockExpr | test.rs:11:5:15:5 | IfExpr | semmle.label | |
| test.rs:11:15:13:5 | BlockExpr | test.rs:11:5:15:5 | IfExpr | semmle.order | 1 |
| test.rs:12:9:12:9 | LiteralExpr | test.rs:11:15:13:5 | BlockExpr | semmle.label | |
| test.rs:12:9:12:9 | LiteralExpr | test.rs:11:15:13:5 | BlockExpr | semmle.order | 1 |
| test.rs:13:12:15:5 | BlockExpr | test.rs:11:5:15:5 | IfExpr | semmle.label | |
| test.rs:13:12:15:5 | BlockExpr | test.rs:11:5:15:5 | IfExpr | semmle.order | 1 |
| test.rs:14:9:14:9 | PathExpr | test.rs:14:13:14:13 | LiteralExpr | semmle.label | |
| test.rs:14:9:14:9 | PathExpr | test.rs:14:13:14:13 | LiteralExpr | semmle.order | 1 |
| test.rs:14:9:14:13 | BinaryOpExpr | test.rs:13:12:15:5 | BlockExpr | semmle.label | |
| test.rs:14:9:14:13 | BinaryOpExpr | test.rs:13:12:15:5 | BlockExpr | semmle.order | 1 |
| test.rs:14:13:14:13 | LiteralExpr | test.rs:14:9:14:13 | BinaryOpExpr | semmle.label | |
| test.rs:14:13:14:13 | LiteralExpr | test.rs:14:9:14:13 | BinaryOpExpr | semmle.order | 1 |

View File

@@ -0,0 +1,15 @@
/**
* @id rust/controlflow/cfg
*/
import rust
import codeql.rust.controlflow.ControlFlowGraph
import TestUtils
class MyRelevantNode extends CfgNode {
MyRelevantNode() { toBeTested(this.getScope()) }
string getOrderDisambiguation() { result = "" }
}
import codeql.rust.controlflow.internal.ControlFlowGraphImpl::TestOutput<MyRelevantNode>

View File

@@ -0,0 +1,16 @@
fn main() -> i64 {
if "foo" == "bar" {
decrement(0)
} else {
decrement(5)
}
}
fn decrement(n: i64) -> i64 {
12;
if n == 0 {
0
} else {
n - 1
}
}