mirror of
https://github.com/github/codeql.git
synced 2025-12-16 16:53:25 +01:00
Merge pull request #17415 from paldepind/rust-control-flow-graph
Rust: Basic control flow graph setup
This commit is contained in:
263
rust/ql/lib/codeql/rust/controlflow/BasicBlocks.qll
Normal file
263
rust/ql/lib/codeql/rust/controlflow/BasicBlocks.qll
Normal 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))
|
||||
}
|
||||
}
|
||||
38
rust/ql/lib/codeql/rust/controlflow/ControlFlowGraph.qll
Normal file
38
rust/ql/lib/codeql/rust/controlflow/ControlFlowGraph.qll
Normal 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 }
|
||||
}
|
||||
93
rust/ql/lib/codeql/rust/controlflow/internal/Completion.qll
Normal file
93
rust/ql/lib/codeql/rust/controlflow/internal/Completion.qll
Normal 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) }
|
||||
@@ -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 { }
|
||||
50
rust/ql/lib/codeql/rust/controlflow/internal/PrintCfg.ql
Normal file
50
rust/ql/lib/codeql/rust/controlflow/internal/PrintCfg.ql
Normal 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>
|
||||
32
rust/ql/lib/codeql/rust/controlflow/internal/Scope.qll
Normal file
32
rust/ql/lib/codeql/rust/controlflow/internal/Scope.qll
Normal 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)
|
||||
)
|
||||
}
|
||||
17
rust/ql/lib/codeql/rust/controlflow/internal/Splitting.qll
Normal file
17
rust/ql/lib/codeql/rust/controlflow/internal/Splitting.qll
Normal 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() }
|
||||
}
|
||||
@@ -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" }
|
||||
}
|
||||
92
rust/ql/test/library-tests/controlflow/Cfg.expected
Normal file
92
rust/ql/test/library-tests/controlflow/Cfg.expected
Normal 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 |
|
||||
15
rust/ql/test/library-tests/controlflow/Cfg.ql
Normal file
15
rust/ql/test/library-tests/controlflow/Cfg.ql
Normal 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>
|
||||
16
rust/ql/test/library-tests/controlflow/test.rs
Normal file
16
rust/ql/test/library-tests/controlflow/test.rs
Normal 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user