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: Basic control flow graph setup #17415

Merged
merged 4 commits into from
Sep 11, 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
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) =
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
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
Loading