Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
65513b8
Incidental fix to `CaseClause.getAnExpr()`
owen-mc Jun 3, 2026
fc055a8
Initial shared CFG library instantiation for Go
owen-mc Mar 30, 2026
99330a6
Add go/print-cfg
owen-mc May 13, 2026
1c62580
Create cfg node for child of ParenExpr
owen-mc May 13, 2026
984a880
Model non-returning functions in CFG
owen-mc May 19, 2026
f8c2f2c
Tweak `getEnclosingCallable`
owen-mc May 20, 2026
0deb94a
Fix edges to function exit with result variables
owen-mc May 20, 2026
f995363
Do not include comments in the CFG
owen-mc May 21, 2026
32a6187
Use shared CFG implementation of for loops
owen-mc May 21, 2026
22ca59d
Fix CFG for select statements
owen-mc May 21, 2026
13d4eb6
Fix CFG for range loop
owen-mc May 28, 2026
8a62e87
Include receivers in parameter init
owen-mc May 28, 2026
642e567
Fix global value numbering calculation
owen-mc May 28, 2026
9b35117
Produce CFG nodes for more reference expressions, like selector bases
owen-mc May 29, 2026
d582d68
Fix CFG for return instructions
owen-mc May 29, 2026
0244d80
Control flow shouldn't enter another callable
owen-mc May 29, 2026
9b63398
Fix empty switch statements
owen-mc May 29, 2026
016c53c
Accept change in test output
owen-mc May 29, 2026
134cc48
Restore `ExprNode` for `FuncLit`
owen-mc May 29, 2026
b563425
update function-entry additional nodes
owen-mc May 30, 2026
68a4131
Fix range loop CFG
owen-mc May 30, 2026
8099b25
Fix lit-init nodes
owen-mc May 30, 2026
8debf96
Use shared CFG `getIfInit`
owen-mc May 30, 2026
6997936
Go: update expected node names
owen-mc May 30, 2026
0dfa9d7
Add Go CFG consistency query
owen-mc Jun 1, 2026
53a6afa
Fix treatment of `ParenExpr`
owen-mc Jun 3, 2026
8908dc4
Restore `ConditionGuardNode`
owen-mc Jun 3, 2026
c37f235
Accept test output change names
owen-mc Jun 3, 2026
de96f6c
Fix ConditionGuardNode
owen-mc Jun 3, 2026
2453482
Accept changes
owen-mc Jun 3, 2026
7039942
Fix CFG for expressionless switch statements
owen-mc Jun 3, 2026
0f6bccf
accept test changes
owen-mc Jun 3, 2026
3cd6a27
Include implicit type switch var in CFG
owen-mc Jun 3, 2026
db8b2cf
Allow overriding endAbruptCompletion for callables
owen-mc Jun 16, 2026
28afda1
Shared CFG: add callableExitStep hook for routing epilogue tails to e…
owen-mc Jun 16, 2026
f5eef7d
Go CFG: anchor result-read epilogue on Normal Exit via new hooks
owen-mc Jun 16, 2026
449732a
Shared CFG: add hooks for reachability-gated exit epilogue
owen-mc Jun 16, 2026
13bf978
Go CFG: run deferred calls at function exit in LIFO order
owen-mc Jun 16, 2026
138a1c3
Fix calls for defer statements
owen-mc Jun 16, 2026
326fa74
Update query for unreachable statements
owen-mc Jun 19, 2026
cae623d
Test changes to be checked
owen-mc Jun 19, 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();

Check warning

Code scanning / CodeQL

Dead code Warning

This code is never used, and it's not publicly exported.

private predicate selectedSourceFileAlias = selectedSourceFile/0;

Check warning

Code scanning / CodeQL

Dead code Warning

This code is never used, and it's not publicly exported.

external int selectedSourceLine();

Check warning

Code scanning / CodeQL

Dead code Warning

This code is never used, and it's not publicly exported.

private predicate selectedSourceLineAlias = selectedSourceLine/0;

Check warning

Code scanning / CodeQL

Dead code Warning

This code is never used, and it's not publicly exported.

external int selectedSourceColumn();

Check warning

Code scanning / CodeQL

Dead code Warning

This code is never used, and it's not publicly exported.

private predicate selectedSourceColumnAlias = selectedSourceColumn/0;

Check warning

Code scanning / CodeQL

Dead code Warning

This code is never used, and it's not publicly exported.

module ViewCfgQueryInput implements GoCfg::ControlFlow::ViewCfgQueryInputSig<File> {
predicate selectedSourceFile = selectedSourceFileAlias/0;

Check warning

Code scanning / CodeQL

Dead code Warning

This code is never used, and it's not publicly exported.

predicate selectedSourceLine = selectedSourceLineAlias/0;

Check warning

Code scanning / CodeQL

Dead code Warning

This code is never used, and it's not publicly exported.

predicate selectedSourceColumn = selectedSourceColumnAlias/0;

Check warning

Code scanning / CodeQL

Dead code Warning

This code is never used, and it's not publicly exported.

predicate cfgScopeSpan(

Check warning

Code scanning / CodeQL

Dead code Warning

This code is never used, and it's not publicly exported.
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

Check warning

Code scanning / CodeQL

Redundant cast Warning

Redundant cast to
File
.
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
Loading