Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
1e05710
Initial plan
Copilot Jun 22, 2026
412f4c2
Incidental fix to `CaseClause.getAnExpr()`
owen-mc Jun 3, 2026
a26cbad
Initial shared CFG library instantiation for Go
owen-mc Mar 30, 2026
3937a96
Add go/print-cfg
owen-mc May 13, 2026
fbd82ff
Create cfg node for child of ParenExpr
owen-mc May 13, 2026
6a1649d
Model non-returning functions in CFG
owen-mc May 19, 2026
c136d3e
Tweak `getEnclosingCallable`
owen-mc May 20, 2026
01568dd
Fix edges to function exit with result variables
owen-mc May 20, 2026
e328a5d
Do not include comments in the CFG
owen-mc May 21, 2026
a5e7612
Use shared CFG implementation of for loops
owen-mc May 21, 2026
d6d254f
Fix CFG for select statements
owen-mc May 21, 2026
008cbba
Fix CFG for range loop
owen-mc May 28, 2026
8fb4875
Include receivers in parameter init
owen-mc May 28, 2026
cffc3c7
Fix global value numbering calculation
owen-mc May 28, 2026
1bcdbd2
Produce CFG nodes for more reference expressions, like selector bases
owen-mc May 29, 2026
c1dfeac
Fix CFG for return instructions
owen-mc May 29, 2026
8c78599
Control flow shouldn't enter another callable
owen-mc May 29, 2026
dc7867b
Fix empty switch statements
owen-mc May 29, 2026
7430209
Accept change in test output
owen-mc May 29, 2026
5962a22
Restore `ExprNode` for `FuncLit`
owen-mc May 29, 2026
4d627be
update function-entry additional nodes
owen-mc May 30, 2026
63464ec
Fix range loop CFG
owen-mc May 30, 2026
c4011f1
Fix lit-init nodes
owen-mc May 30, 2026
3a955b1
Use shared CFG `getIfInit`
owen-mc May 30, 2026
4409d82
Go: update expected node names
owen-mc May 30, 2026
85d198e
Add Go CFG consistency query
owen-mc Jun 1, 2026
c5895a8
Fix treatment of `ParenExpr`
owen-mc Jun 3, 2026
f436d89
Restore `ConditionGuardNode`
owen-mc Jun 3, 2026
f5b0127
Accept test output change names
owen-mc Jun 3, 2026
4a3fbf3
Fix ConditionGuardNode
owen-mc Jun 3, 2026
e5cd376
Accept changes
owen-mc Jun 3, 2026
ebe24b2
Fix CFG for expressionless switch statements
owen-mc Jun 3, 2026
9090ed3
accept test changes
owen-mc Jun 3, 2026
b0e886f
Include implicit type switch var in CFG
owen-mc Jun 3, 2026
0e50b3c
Allow overriding endAbruptCompletion for callables
owen-mc Jun 16, 2026
ac1b126
Shared CFG: add callableExitStep hook for routing epilogue tails to e…
owen-mc Jun 16, 2026
71cc564
Go CFG: anchor result-read epilogue on Normal Exit via new hooks
owen-mc Jun 16, 2026
5b79e0d
Shared CFG: add hooks for reachability-gated exit epilogue
owen-mc Jun 16, 2026
8fd9c31
Go CFG: run deferred calls at function exit in LIFO order
owen-mc Jun 16, 2026
80bd611
Fix calls for defer statements
owen-mc Jun 16, 2026
7bfab95
Update query for unreachable statements
owen-mc Jun 19, 2026
2e1289d
Test changes to be checked
owen-mc Jun 19, 2026
aa7bdb5
Migrate Go expression switch CFG to shared library; handle fallthroug…
Copilot Jun 23, 2026
73bf061
Fix switch-case sanitizer edge for shared CFG and accept CFG expected
Copilot Jun 23, 2026
cc80eca
Update SSA expected files for new shared-CFG node locations
Copilot Jun 23, 2026
d5f8665
Migrate Go type switches to shared CFG switch model (Option B)
Copilot Jun 23, 2026
9f488f6
Small refactors
owen-mc Jun 24, 2026
8e99548
Shared: allow destructuring in ForeachStmt
owen-mc Jun 24, 2026
67488a8
Go: adopt ForeachStmt with destructuring
owen-mc Jun 24, 2026
62f9073
f initial
owen-mc Jun 24, 2026
6fa89ef
Fix break in select statements
owen-mc Jun 25, 2026
030af8e
Remove dead "send" additional CFG node and unused SendInstruction
Copilot Jun 25, 2026
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
3 changes: 3 additions & 0 deletions go/ql/consistency-queries/CfgConsistency.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import go
private import semmle.go.controlflow.ControlFlowGraphShared
import GoCfg::ControlFlow::Consistency
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.
53 changes: 53 additions & 0 deletions go/ql/lib/printCfg.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/**
* @name Print CFG
* @description Produces a representation of a file's Control Flow Graph.
* This query is used by the VS Code extension.
* @id go/print-cfg
* @kind graph
* @tags ide-contextual-queries/print-cfg
*/

import go
import semmle.go.controlflow.ControlFlowGraph
private import semmle.go.controlflow.ControlFlowGraphShared

external string selectedSourceFile();

private predicate selectedSourceFileAlias = selectedSourceFile/0;

external int selectedSourceLine();

private predicate selectedSourceLineAlias = selectedSourceLine/0;

external int selectedSourceColumn();

private predicate selectedSourceColumnAlias = selectedSourceColumn/0;

module ViewCfgQueryInput implements GoCfg::ControlFlow::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().getStartLine() = startLine and
scope.getLocation().getStartColumn() = startColumn and
exists(Location loc |
loc.getEndLine() = endLine and
loc.getEndColumn() = endColumn and
loc = scope.(FuncDef).getBody().getLocation()
)
or
file = scope.(File) and
startLine = 1 and
startColumn = 1 and
endLine = file.getNumberOfLines() and
endColumn = 999999
}
}

import GoCfg::ControlFlow::ViewCfgQuery<File, ViewCfgQueryInput>
2 changes: 1 addition & 1 deletion go/ql/lib/semmle/go/Concepts.qll
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,7 @@ private class HeuristicLoggerFunction extends Method {
)
}

override predicate mayReturnNormally() { logFunctionPrefix != "Fatal" }
override predicate mustNotReturnNormally() { logFunctionPrefix = "Fatal" }

override predicate mustPanic() { logFunctionPrefix = "Panic" }
}
Expand Down
2 changes: 1 addition & 1 deletion go/ql/lib/semmle/go/PrintAst.qll
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Provides queries to pretty-print a Go AST as a graph.
*/
overlay[local]
overlay[local?]
module;

import go
Expand Down
15 changes: 13 additions & 2 deletions go/ql/lib/semmle/go/Scopes.qll
Original file line number Diff line number Diff line change
Expand Up @@ -437,11 +437,12 @@ class Function extends ValueEntity, @functionobject {
* This predicate is an over-approximation: it may hold for functions that can never
* return normally, but it never fails to hold for functions that can.
*
* Note this is declared here and not in `DeclaredFunction` so that library models can override this
* by extending `Function` rather than having to remember to extend `DeclaredFunction`.
* Library models should not override this predicate; override `mustNotReturnNormally`
* instead, so that the control-flow graph construction can take the model into account.
*/
predicate mayReturnNormally() {
not this.mustPanic() and
not this.mustNotReturnNormally() and
(ControlFlow::mayReturnNormally(this.getFuncDecl()) or not exists(this.getBody()))
}

Expand All @@ -461,6 +462,16 @@ class Function extends ValueEntity, @functionobject {
*/
predicate mustPanic() { none() }

/**
* Holds if calling this function never returns normally (for example because it
* always panics, exits the process, or loops forever).
*
* Unlike `mayReturnNormally`, this predicate must be defined without reference to
* the control-flow graph, so that it can be used during CFG construction to
* suppress normal-flow successors of calls to this function.
*/
predicate mustNotReturnNormally() { none() }

/** Gets the number of parameters of this function. */
int getNumParameter() { result = this.getType().(SignatureType).getNumParameter() }

Expand Down
2 changes: 1 addition & 1 deletion go/ql/lib/semmle/go/Stmt.qll
Original file line number Diff line number Diff line change
Expand Up @@ -761,7 +761,7 @@ class CaseClause extends @caseclause, Stmt, ScopeNode {
*
* Note that the default clause does not have any expressions.
*/
Expr getAnExpr() { result = this.getAChildExpr() }
Expr getAnExpr() { result = this.getExpr(_) }

/**
* Gets the number of expressions of this `case` clause.
Expand Down
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 }
}

module Cfg = BB::Make<Location, Input>;

class BasicBlock = Cfg::BasicBlock;

class EntryBasicBlock = Cfg::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() }
}
Loading