Skip to content

Commit

Permalink
Merge pull request #17415 from paldepind/rust-control-flow-graph
Browse files Browse the repository at this point in the history
Rust: Basic control flow graph setup
  • Loading branch information
paldepind authored Sep 11, 2024
2 parents 4398421 + 857edb7 commit fea6017
Show file tree
Hide file tree
Showing 11 changed files with 790 additions and 0 deletions.
263 changes: 263 additions & 0 deletions rust/ql/lib/codeql/rust/controlflow/BasicBlocks.qll
Original file line number Diff line number Diff line change
@@ -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) =

Check warning

Code scanning / CodeQL

Missing QLDoc for parameter Warning

The QLDoc has no documentation for bbStart, but the QLDoc mentions bb
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 changes: 38 additions & 0 deletions rust/ql/lib/codeql/rust/controlflow/ControlFlowGraph.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/** Provides classes representing the control flow graph. */

private import rust
private import internal.ControlFlowGraphImpl
private import internal.Completion

Check warning

Code scanning / CodeQL

Redundant import Warning

Redundant import, the module is already imported inside
internal.ControlFlowGraphImpl
.
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 changes: 93 additions & 0 deletions rust/ql/lib/codeql/rust/controlflow/internal/Completion.qll
Original file line number Diff line number Diff line change
@@ -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) }
Loading

0 comments on commit fea6017

Please sign in to comment.