Add base setup for control flow graph construction

This commit is contained in:
Simon Friis Vindum
2024-09-09 17:43:03 +02:00
parent 4f90f5fb4c
commit 91d5171d90
12 changed files with 614 additions and 0 deletions

View File

@@ -0,0 +1,262 @@
private import rust
private import ControlFlowGraph
private import internal.SuccessorType
private import SuccessorTypes
private import internal.ControlFlowGraphImpl as Impl
private import codeql.rust.generated.Raw
private import codeql.rust.generated.Synth
/**
* A basic block, that is, a maximal straight-line sequence of control flow nodes
* without branches or joins.
*/
class BasicBlock 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 BasicBlock {
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 BasicBlock {
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 BasicBlock {
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 BasicBlock {
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 BasicBlock {
JoinBlockPredecessor() { this.getASuccessor() instanceof JoinBlock }
}
/** A basic block that terminates in a condition, splitting the subsequent control flow. */
class ConditionBlock extends BasicBlock {
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,36 @@
/** Provides classes representing the control flow graph. */
private import rust
private import internal.ControlFlowGraphImpl
private import internal.Completion
private import internal.SuccessorType
private import codeql.rust.controlflow.BasicBlocks
import internal.Scope
/**
* 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.
*/
class CfgNode extends Node {
/** Gets the file of this control flow node. */
final File getFile() { result = this.getLocation().getFile() }
/** Gets a successor node of a given type, if any. */
final CfgNode getASuccessor(SuccessorType t) { result = super.getASuccessor(t) }
/** Gets an immediate successor, if any. */
final CfgNode getASuccessor() { result = this.getASuccessor(_) }
/** Gets an immediate predecessor node of a given flow type, if any. */
final CfgNode getAPredecessor(SuccessorType t) { result.getASuccessor(t) = this }
/** Gets an immediate predecessor, if any. */
final 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,84 @@
private import rust
private import codeql.rust.controlflow.ControlFlowGraph
private import SuccessorType
private import SuccessorTypes
private newtype TCompletion =
TSimpleCompletion() or
TBooleanCompletion(boolean b) { b in [false, true] } 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(If 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 Return }
override string toString() { result = "return" }
}

View File

@@ -0,0 +1 @@
import ControlFlowGraphImplSpecific::CfgImpl

View File

@@ -0,0 +1,63 @@
import codeql.controlflow.Cfg
import rust as Rust
private import SuccessorType as ST
private import Scope as Scope
module CfgInput implements InputSig<Rust::Location> {
private import Completion as C
private import Splitting as S
class AstNode = Rust::AstNode;
class Completion = C::Completion;
predicate completionIsNormal(Completion c) { c instanceof C::NormalCompletion }
predicate completionIsSimple(Completion c) { c instanceof C::SimpleCompletion }
predicate completionIsValidFor(Completion c, AstNode e) { c.isValidFor(e) }
/** 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::SuccessorTypes::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::SuccessorTypes::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<Rust::Location, CfgInput>;

View File

@@ -0,0 +1,51 @@
/**
* @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.ControlFlowGraph
private import codeql.rust.controlflow.internal.ControlFlowGraphImpl as Impl
private import codeql.rust.controlflow.internal.ControlFlowGraphImplSpecific
/**
* 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;
module ViewCfgQueryInput implements Impl::ViewCfgQueryInputSig<File> {
predicate selectedSourceFile = selectedSourceFileAlias/0;
predicate selectedSourceLine = selectedSourceLineAlias/0;
predicate selectedSourceColumn = selectedSourceColumnAlias/0;
predicate cfgScopeSpan(
CfgInput::CfgScope scope, File file, int startLine, int startColumn, int endLine, int endColumn
) {
file = scope.getFile() and
scope.getLocation().hasLocationInfo(_, startLine, startColumn, endLine, endColumn)
}
}
import Impl::ViewCfgQuery<File, ViewCfgQueryInput>

View File

@@ -0,0 +1,36 @@
private import rust
private import Completion
private import codeql.rust.generated.ParentChild
abstract class CfgScope extends AstNode { }
class FunctionScope extends CfgScope, Function { }
cached
private module Cached {
/**
* 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`. */
cached
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)
)
}
import Cached

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,44 @@
cached
newtype TSuccessorType =
TSuccessorSuccessor() or
TBooleanSuccessor(boolean b) { b in [false, true] } or
TReturnSuccessor()
/** The type of a control flow successor. */
class SuccessorType extends TSuccessorType {
/** Gets a textual representation of successor type. */
string toString() { none() }
}
/** Provides different types of control flow successor types. */
module SuccessorTypes {
/** A normal control flow successor. */
class NormalSuccessor extends SuccessorType, TSuccessorSuccessor {
final override string toString() { result = "successor" }
}
/** A conditional control flow successor. */
abstract class ConditionalSuccessor extends SuccessorType {
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. */
class BooleanSuccessor extends ConditionalSuccessor, TBooleanSuccessor {
BooleanSuccessor() { this = TBooleanSuccessor(value) }
}
/**
* A `return` control flow successor.
*/
class ReturnSuccessor extends SuccessorType, TReturnSuccessor {
final override string toString() { result = "return" }
}
}

View File

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

View File

@@ -0,0 +1,7 @@
fn main() {
if "foo" == "bar" {
println!("foobar");
} else {
println!("baz")
}
}