Skip to content
Draft
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
4 changes: 4 additions & 0 deletions go/ql/lib/change-notes/2026-03-30-shared-cfg-library.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
category: fix
---
* The Go control flow graph implementation has been migrated to use the shared CFG library. This is an internal change with no user-visible API changes.
59 changes: 10 additions & 49 deletions go/ql/lib/semmle/go/controlflow/BasicBlocks.qll
Original file line number Diff line number Diff line change
Expand Up @@ -5,66 +5,27 @@ overlay[local]
module;

import go
private import ControlFlowGraphImpl
private import codeql.controlflow.BasicBlock as BB
private import codeql.controlflow.SuccessorType
private import ControlFlowGraphShared

private module Input implements BB::InputSig<Location> {
/** A delineated part of the AST with its own CFG. */
class CfgScope = ControlFlow::Root;
/** A basic block in the control-flow graph. */
class BasicBlock = GoCfg::Cfg::BasicBlock;

/** The class of control flow nodes. */
class Node = ControlFlowNode;

/** Gets the CFG scope in which this node occurs. */
CfgScope nodeGetCfgScope(Node node) { node.getRoot() = result }

/** Gets an immediate successor of this node. */
Node nodeGetASuccessor(Node node, SuccessorType t) {
result = node.getASuccessor() and
(
not result instanceof ControlFlow::ConditionGuardNode and t instanceof DirectSuccessor
or
t.(BooleanSuccessor).getValue() = result.(ControlFlow::ConditionGuardNode).getOutcome()
)
}

/**
* Holds if `node` represents an entry node to be used when calculating
* dominance.
*/
predicate nodeIsDominanceEntry(Node node) { node instanceof EntryNode }

/**
* Holds if `node` represents an exit node to be used when calculating
* post dominance.
*/
predicate nodeIsPostDominanceExit(Node node) { node instanceof ExitNode }
}

private module BbImpl = BB::Make<Location, Input>;

class BasicBlock = BbImpl::BasicBlock;

class EntryBasicBlock = BbImpl::EntryBasicBlock;

cached
private predicate reachableBB(BasicBlock bb) {
bb instanceof EntryBasicBlock
or
exists(BasicBlock predBB | predBB.getASuccessor(_) = bb | reachableBB(predBB))
}
/** An entry basic block. */
class EntryBasicBlock = GoCfg::Cfg::EntryBasicBlock;

/**
* A basic block that is reachable from an entry basic block.
*
* Since the shared CFG library only creates nodes for reachable code,
* all basic blocks are reachable by construction.
*/
class ReachableBasicBlock extends BasicBlock {
ReachableBasicBlock() { reachableBB(this) }
ReachableBasicBlock() { any() }
}

/**
* A reachable basic block with more than one predecessor.
*/
class ReachableJoinBlock extends ReachableBasicBlock {
ReachableJoinBlock() { this.getFirstNode().isJoin() }
ReachableJoinBlock() { this.getFirstNode().(ControlFlow::Node).isJoin() }
}
108 changes: 52 additions & 56 deletions go/ql/lib/semmle/go/controlflow/ControlFlowGraph.qll
Original file line number Diff line number Diff line change
Expand Up @@ -5,69 +5,59 @@ overlay[local]
module;

import go
private import ControlFlowGraphImpl
private import ControlFlowGraphShared

/** Provides helper predicates for mapping btween CFG nodes and the AST. */
/** Provides helper predicates for mapping between CFG nodes and the AST. */
module ControlFlow {
/** A file or function with which a CFG is associated. */
/** A function with which a CFG is associated. */
class Root extends AstNode {
Root() { exists(this.(File).getADecl()) or exists(this.(FuncDef).getBody()) }
Root() { exists(this.(FuncDef).getBody()) }

/** Holds if `nd` belongs to this file or function. */
predicate isRootOf(AstNode nd) {
this = nd.getEnclosingFunction()
or
not exists(nd.getEnclosingFunction()) and
this = nd.getFile()
}
/** Holds if `nd` belongs to this function. */
predicate isRootOf(AstNode nd) { this = nd.getEnclosingFunction() }

/** Gets the synthetic entry node of the CFG for this file or function. */
/** Gets the synthetic entry node of the CFG for this function. */
EntryNode getEntryNode() { result = ControlFlow::entryNode(this) }

/** Gets the synthetic exit node of the CFG for this file or function. */
/** Gets the synthetic exit node of the CFG for this function. */
ExitNode getExitNode() { result = ControlFlow::exitNode(this) }
}

/**
* A node in the intra-procedural control-flow graph of a Go function or file.
* A node in the intra-procedural control-flow graph of a Go function.
*
* Nodes correspond to expressions and statements that compute a value or perform
* an operation (as opposed to providing syntactic structure or type information).
*
* There are also synthetic entry and exit nodes for each Go function and file
* There are also synthetic entry and exit nodes for each Go function
* that mark the beginning and the end, respectively, of the execution of the
* function and the loading of the file.
* function.
*/
class Node extends TControlFlowNode {
/** Gets a node that directly follows this one in the control-flow graph. */
Node getASuccessor() { result = CFG::succ(this) }

/** Gets a node that directly precedes this one in the control-flow graph. */
Node getAPredecessor() { this = result.getASuccessor() }

class Node extends GoCfg::ControlFlowNode {
/** Holds if this is a node with more than one successor. */
predicate isBranch() { strictcount(this.getASuccessor()) > 1 }

/** Holds if this is a node with more than one predecessor. */
predicate isJoin() { strictcount(this.getAPredecessor()) > 1 }

/** Holds if this is the first control-flow node in `subtree`. */
predicate isFirstNodeOf(AstNode subtree) { CFG::firstNode(subtree, this) }

/** Holds if this node is the (unique) entry node of a function or file. */
predicate isEntryNode() { this instanceof MkEntryNode }
predicate isFirstNodeOf(AstNode subtree) {
this.isBefore(subtree)
or
this.injects(subtree)
}

/** Holds if this node is the (unique) exit node of a function or file. */
predicate isExitNode() { this instanceof MkExitNode }
/** Holds if this node is the (unique) entry node of a function. */
predicate isEntryNode() { this instanceof GoCfg::ControlFlow::EntryNode }

/** Gets the basic block to which this node belongs. */
BasicBlock getBasicBlock() { result.getANode() = this }
/** Holds if this node is the (unique) exit node of a function. */
predicate isExitNode() { this instanceof GoCfg::ControlFlow::ExitNode }

/** Holds if this node dominates `dominee` in the control-flow graph. */
overlay[caller?]
pragma[inline]
predicate dominatesNode(ControlFlow::Node dominee) {
exists(ReachableBasicBlock thisbb, ReachableBasicBlock dbb, int i, int j |
exists(GoCfg::Cfg::BasicBlock thisbb, GoCfg::Cfg::BasicBlock dbb, int i, int j |
this = thisbb.getNode(i) and dominee = dbb.getNode(j)
|
thisbb.strictlyDominates(dbb)
Expand All @@ -76,20 +66,12 @@ module ControlFlow {
)
}

/** Gets the innermost function or file to which this node belongs. */
Root getRoot() { none() }
/** Gets the innermost function to which this node belongs. */
Root getRoot() { result = this.getEnclosingCallable() }

/** Gets the file to which this node belongs. */
File getFile() { result = this.getLocation().getFile() }

/**
* Gets a textual representation of this control flow node.
*/
string toString() { result = "control-flow node" }

/** Gets the source location for this element. */
Location getLocation() { none() }

/**
* DEPRECATED: Use `getLocation()` instead.
*
Expand All @@ -113,6 +95,12 @@ module ControlFlow {
}
}

/** A synthetic entry node for a function. */
class EntryNode extends Node instanceof GoCfg::ControlFlow::EntryNode { }

/** A synthetic exit node for a function. */
class ExitNode extends Node instanceof GoCfg::ControlFlow::ExitNode { }

/**
* A control-flow node that initializes or updates the value of a constant, a variable,
* a field, or an (array, slice, or map) element.
Expand Down Expand Up @@ -172,7 +160,7 @@ module ControlFlow {
exists(IR::FieldTarget trg | trg = super.getLhs() |
(
trg.getBase() = base or
trg.getBase() = MkImplicitDeref(base.(IR::EvalInstruction).getExpr())
trg.getBase() = IR::implicitDerefInstruction(base.(IR::EvalInstruction).getExpr())
) and
trg.getField() = f and
super.getRhs() = rhs
Expand Down Expand Up @@ -220,7 +208,7 @@ module ControlFlow {
exists(IR::ElementTarget trg | trg = super.getLhs() |
(
trg.getBase() = base or
trg.getBase() = MkImplicitDeref(base.(IR::EvalInstruction).getExpr())
trg.getBase() = IR::implicitDerefInstruction(base.(IR::EvalInstruction).getExpr())
) and
trg.getIndex() = index and
super.getRhs() = rhs
Expand Down Expand Up @@ -250,11 +238,15 @@ module ControlFlow {
* A control-flow node recording the fact that a certain expression has a known
* Boolean value at this point in the program.
*/
class ConditionGuardNode extends IR::Instruction, MkConditionGuardNode {
class ConditionGuardNode extends Node {
Expr cond;
boolean outcome;

ConditionGuardNode() { this = MkConditionGuardNode(cond, outcome) }
ConditionGuardNode() {
this.isAfterTrue(cond) and outcome = true
or
this.isAfterFalse(cond) and outcome = false
}

private predicate ensuresAux(Expr expr, boolean b) {
expr = cond and b = outcome
Expand Down Expand Up @@ -320,29 +312,27 @@ module ControlFlow {
boolean getOutcome() { result = outcome }

override Root getRoot() { result.isRootOf(cond) }

override string toString() { result = cond + " is " + outcome }

override Location getLocation() { result = cond.getLocation() }
}

/**
* Gets the entry node of function or file `root`.
* Gets the entry node of function `root`.
*/
Node entryNode(Root root) { result = MkEntryNode(root) }
EntryNode entryNode(Root root) { result.getEnclosingCallable() = root }

/**
* Gets the exit node of function or file `root`.
* Gets the exit node of function `root`.
*/
Node exitNode(Root root) { result = MkExitNode(root) }
ExitNode exitNode(Root root) { result.getEnclosingCallable() = root }

/**
* Holds if the function `f` may return without panicking, exiting the process, or looping forever.
*
* This is defined conservatively, and so may also hold of a function that in fact
* cannot return normally, but never fails to hold of a function that can return normally.
*/
predicate mayReturnNormally(FuncDecl f) { CFG::mayReturnNormally(f.getBody()) }
predicate mayReturnNormally(FuncDecl f) {
exists(GoCfg::ControlFlow::NormalExitNode exit | exit.getEnclosingCallable() = f)
}

/**
* Holds if `pred` is the node for the case `testExpr` in an expression
Expand All @@ -352,7 +342,13 @@ module ControlFlow {
predicate isSwitchCaseTestPassingEdge(
ControlFlow::Node pred, ControlFlow::Node succ, Expr switchExpr, Expr testExpr
) {
CFG::isSwitchCaseTestPassingEdge(pred, succ, switchExpr, testExpr)
exists(ExpressionSwitchStmt ess, CaseClause cc, int i |
ess.getExpr() = switchExpr and
cc = ess.getACase() and
testExpr = cc.getExpr(i) and
pred.isAfter(testExpr) and
succ.isFirstNodeOf(cc.getStmt(0))
)
}
}

Expand Down
Loading