From 389cd5d648e31b695f7240a1e9c711092b6fdec0 Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Tue, 3 Feb 2026 15:33:55 +0100 Subject: [PATCH 1/4] Cfg: Extract CFG pretty-printing code. --- shared/controlflow/codeql/controlflow/Cfg.qll | 157 +-------------- .../codeql/controlflow/PrintGraph.qll | 184 ++++++++++++++++++ 2 files changed, 193 insertions(+), 148 deletions(-) create mode 100644 shared/controlflow/codeql/controlflow/PrintGraph.qll diff --git a/shared/controlflow/codeql/controlflow/Cfg.qll b/shared/controlflow/codeql/controlflow/Cfg.qll index 62eebe8e183e..6f9a3adfec46 100644 --- a/shared/controlflow/codeql/controlflow/Cfg.qll +++ b/shared/controlflow/codeql/controlflow/Cfg.qll @@ -1310,161 +1310,22 @@ module MakeWithSplitting< } } - /** A node to be included in the output of `TestOutput`. */ - signature class RelevantNodeSig extends Node; + private import PrintGraph as Pp - /** - * Import this module into a `.ql` file to output a CFG. The - * graph is restricted to nodes from `RelevantNode`. - */ - module TestOutput { - /** Holds if `pred -> succ` is an edge in the CFG. */ - query predicate edges(RelevantNode pred, RelevantNode succ, string label) { - label = - strictconcat(SuccessorType t, string s | - succ = getASuccessor(pred, t) and - if t instanceof DirectSuccessor then s = "" else s = t.toString() - | - s, ", " order by s - ) - } - - /** - * Provides logic for representing a CFG as a [Mermaid diagram](https://mermaid.js.org/). - */ - module Mermaid { - private string nodeId(RelevantNode n) { - result = - any(int i | - n = - rank[i](RelevantNode p, string filePath, int startLine, int startColumn, int endLine, - int endColumn | - p.getLocation() - .hasLocationInfo(filePath, startLine, startColumn, endLine, endColumn) - | - p order by filePath, startLine, startColumn, endLine, endColumn, p.toString() - ) - ).toString() - } - - private string nodes() { - result = - concat(RelevantNode n, string id, string text | - id = nodeId(n) and - text = n.toString() - | - id + "[\"" + text + "\"]", "\n" order by id - ) - } + private module PrintGraphInput implements Pp::InputSig { + class Callable = CfgScope; - private string edge(RelevantNode pred, RelevantNode succ) { - edges(pred, succ, _) and - exists(string label | - edges(pred, succ, label) and - if label = "" - then result = nodeId(pred) + " --> " + nodeId(succ) - else result = nodeId(pred) + " -- " + label + " --> " + nodeId(succ) - ) - } - - private string edges() { - result = - concat(RelevantNode pred, RelevantNode succ, string edge, string filePath, int startLine, - int startColumn, int endLine, int endColumn | - edge = edge(pred, succ) and - pred.getLocation().hasLocationInfo(filePath, startLine, startColumn, endLine, endColumn) - | - edge, "\n" - order by - filePath, startLine, startColumn, endLine, endColumn, pred.toString() - ) - } - - /** Holds if the Mermaid representation is `s`. */ - query predicate mermaid(string s) { s = "flowchart TD\n" + nodes() + "\n\n" + edges() } + class ControlFlowNode extends Node { + Callable getEnclosingCallable() { result = this.getScope() } } - } - - /** Provides the input to `ViewCfgQuery`. */ - signature module ViewCfgQueryInputSig { - /** The source file selected in the IDE. Should be an `external` predicate. */ - string selectedSourceFile(); - - /** The source line selected in the IDE. Should be an `external` predicate. */ - int selectedSourceLine(); - /** The source column selected in the IDE. Should be an `external` predicate. */ - int selectedSourceColumn(); - - /** - * Holds if CFG scope `scope` spans column `startColumn` of line `startLine` to - * column `endColumn` of line `endLine` in `file`. - */ - predicate cfgScopeSpan( - CfgScope scope, File file, int startLine, int startColumn, int endLine, int endColumn - ); - } - - /** - * Provides an implementation for a `View CFG` query. - * - * Import this module into a `.ql` that looks like - * - * ```ql - * @name Print CFG - * @description Produces a representation of a file's Control Flow Graph. - * This query is used by the VS Code extension. - * @id /print-cfg - * @kind graph - * @tags ide-contextual-queries/print-cfg - * ``` - */ - module ViewCfgQuery ViewCfgQueryInput> { - private import ViewCfgQueryInput - - bindingset[file, line, column] - private CfgScope smallestEnclosingScope(File file, int line, int column) { - result = - min(CfgScope scope, int startLine, int startColumn, int endLine, int endColumn | - cfgScopeSpan(scope, file, startLine, startColumn, endLine, endColumn) and - ( - startLine < line - or - startLine = line and startColumn <= column - ) and - ( - endLine > line - or - endLine = line and endColumn >= column - ) - | - scope order by startLine desc, startColumn desc, endLine, endColumn - ) - } - - private import IdeContextual - - private class RelevantNode extends Node { - RelevantNode() { - this.getScope() = - smallestEnclosingScope(getFileBySourceArchiveName(selectedSourceFile()), - selectedSourceLine(), selectedSourceColumn()) - } - - string getOrderDisambiguation() { result = "" } - } - - private module Output = TestOutput; - - import Output::Mermaid - - /** Holds if `pred` -> `succ` is an edge in the CFG. */ - query predicate edges(RelevantNode pred, RelevantNode succ, string attr, string val) { - attr = "semmle.label" and - Output::edges(pred, succ, val) + ControlFlowNode getASuccessor(ControlFlowNode n, SuccessorType t) { + result = n.getASuccessor(t) } } + import Pp::PrintGraph + /** Provides a set of consistency queries. */ module Consistency { /** Holds if `s1` and `s2` are distinct representations of the same set. */ diff --git a/shared/controlflow/codeql/controlflow/PrintGraph.qll b/shared/controlflow/codeql/controlflow/PrintGraph.qll new file mode 100644 index 000000000000..c09aeeff71cb --- /dev/null +++ b/shared/controlflow/codeql/controlflow/PrintGraph.qll @@ -0,0 +1,184 @@ +/** + * Provides modules for printing control flow graphs in VSCode via the "View + * CFG" query. Also provides modules for printing control flow graphs in tests + * and as Mermaid diagrams. + */ + +private import codeql.util.FileSystem +private import codeql.util.Location +private import SuccessorType + +signature module InputSig { + class Callable; + + class ControlFlowNode { + Callable getEnclosingCallable(); + + Location getLocation(); + + string toString(); + } + + ControlFlowNode getASuccessor(ControlFlowNode n, SuccessorType t); +} + +module PrintGraph Input> { + private import Input + + /** A node to be included in the output of `TestOutput`. */ + signature class RelevantNodeSig extends ControlFlowNode; + + /** + * Import this module into a `.ql` file to output a CFG. The + * graph is restricted to nodes from `RelevantNode`. + */ + module TestOutput { + /** Holds if `pred -> succ` is an edge in the CFG. */ + query predicate edges(RelevantNode pred, RelevantNode succ, string label) { + label = + strictconcat(SuccessorType t, string s | + succ = getASuccessor(pred, t) and + if t instanceof DirectSuccessor then s = "" else s = t.toString() + | + s, ", " order by s + ) + } + + /** + * Provides logic for representing a CFG as a [Mermaid diagram](https://mermaid.js.org/). + */ + module Mermaid { + private string nodeId(RelevantNode n) { + result = + any(int i | + n = + rank[i](RelevantNode p, string filePath, int startLine, int startColumn, int endLine, + int endColumn | + p.getLocation() + .hasLocationInfo(filePath, startLine, startColumn, endLine, endColumn) + | + p order by filePath, startLine, startColumn, endLine, endColumn, p.toString() + ) + ).toString() + } + + private string nodes() { + result = + concat(RelevantNode n, string id, string text | + id = nodeId(n) and + text = n.toString() + | + id + "[\"" + text + "\"]", "\n" order by id + ) + } + + private string edge(RelevantNode pred, RelevantNode succ) { + edges(pred, succ, _) and + exists(string label | + edges(pred, succ, label) and + if label = "" + then result = nodeId(pred) + " --> " + nodeId(succ) + else result = nodeId(pred) + " -- " + label + " --> " + nodeId(succ) + ) + } + + private string edges() { + result = + concat(RelevantNode pred, RelevantNode succ, string edge, string filePath, int startLine, + int startColumn, int endLine, int endColumn | + edge = edge(pred, succ) and + pred.getLocation().hasLocationInfo(filePath, startLine, startColumn, endLine, endColumn) + | + edge, "\n" + order by + filePath, startLine, startColumn, endLine, endColumn, pred.toString() + ) + } + + /** Holds if the Mermaid representation is `s`. */ + query predicate mermaid(string s) { s = "flowchart TD\n" + nodes() + "\n\n" + edges() } + } + } + + /** Provides the input to `ViewCfgQuery`. */ + signature module ViewCfgQueryInputSig { + /** The source file selected in the IDE. Should be an `external` predicate. */ + string selectedSourceFile(); + + /** The source line selected in the IDE. Should be an `external` predicate. */ + int selectedSourceLine(); + + /** The source column selected in the IDE. Should be an `external` predicate. */ + int selectedSourceColumn(); + + /** + * Holds if `callable` spans column `startColumn` of line `startLine` to + * column `endColumn` of line `endLine` in `file`. + */ + predicate cfgScopeSpan( + Callable callable, File file, int startLine, int startColumn, int endLine, int endColumn + ); + } + + /** + * Provides an implementation for a `View CFG` query. + * + * Import this module into a `.ql` that looks like + * + * ```ql + * @name Print CFG + * @description Produces a representation of a file's Control Flow Graph. + * This query is used by the VS Code extension. + * @id /print-cfg + * @kind graph + * @tags ide-contextual-queries/print-cfg + * ``` + */ + module ViewCfgQuery ViewCfgQueryInput> { + private import ViewCfgQueryInput + + bindingset[file, line, column] + private Callable smallestEnclosingScope(File file, int line, int column) { + result = + min(Callable callable, int startLine, int startColumn, int endLine, int endColumn | + cfgScopeSpan(callable, file, startLine, startColumn, endLine, endColumn) and + ( + startLine < line + or + startLine = line and startColumn <= column + ) and + ( + endLine > line + or + endLine = line and endColumn >= column + ) + | + callable order by startLine desc, startColumn desc, endLine, endColumn + ) + } + + private import IdeContextual + + final private class FinalControlFlowNode = ControlFlowNode; + + private class RelevantNode extends FinalControlFlowNode { + RelevantNode() { + this.getEnclosingCallable() = + smallestEnclosingScope(getFileBySourceArchiveName(selectedSourceFile()), + selectedSourceLine(), selectedSourceColumn()) + } + + string getOrderDisambiguation() { result = "" } + } + + private module Output = TestOutput; + + import Output::Mermaid + + /** Holds if `pred` -> `succ` is an edge in the CFG. */ + query predicate edges(RelevantNode pred, RelevantNode succ, string attr, string val) { + attr = "semmle.label" and + Output::edges(pred, succ, val) + } + } +} From 2d61fc5309379648577b42fc1d2691b28c03c657 Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Tue, 3 Feb 2026 15:49:27 +0100 Subject: [PATCH 2/4] Java: Add support for "View CFG". --- java/ql/lib/printCfg.ql | 45 +++++++++++++++++++ .../lib/semmle/code/java/ControlFlowGraph.qll | 14 ++++++ 2 files changed, 59 insertions(+) create mode 100644 java/ql/lib/printCfg.ql diff --git a/java/ql/lib/printCfg.ql b/java/ql/lib/printCfg.ql new file mode 100644 index 000000000000..5e3cc22644ef --- /dev/null +++ b/java/ql/lib/printCfg.ql @@ -0,0 +1,45 @@ +/** + * @name Print CFG + * @description Produces a representation of a file's Control Flow Graph. + * This query is used by the VS Code extension. + * @id java/print-cfg + * @kind graph + * @tags ide-contextual-queries/print-cfg + */ + +import java + +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 ViewCfgQueryInputSig { + predicate selectedSourceFile = selectedSourceFileAlias/0; + + predicate selectedSourceLine = selectedSourceLineAlias/0; + + predicate selectedSourceColumn = selectedSourceColumnAlias/0; + + predicate cfgScopeSpan( + Callable callable, File file, int startLine, int startColumn, int endLine, int endColumn + ) { + file = callable.getFile() and + callable.getLocation().getStartLine() = startLine and + callable.getLocation().getStartColumn() = startColumn and + exists(Location loc | + loc.getEndLine() = endLine and + loc.getEndColumn() = endColumn and + loc = callable.getBody().getLocation() + ) + } +} + +import ViewCfgQuery diff --git a/java/ql/lib/semmle/code/java/ControlFlowGraph.qll b/java/ql/lib/semmle/code/java/ControlFlowGraph.qll index a31101888da3..c7a56f7b4159 100644 --- a/java/ql/lib/semmle/code/java/ControlFlowGraph.qll +++ b/java/ql/lib/semmle/code/java/ControlFlowGraph.qll @@ -1775,3 +1775,17 @@ class ConditionNode extends ControlFlow::Node { /** Gets the condition of this `ConditionNode`. */ ExprParent getCondition() { result = this.asExpr() or result = this.asStmt() } } + +private import codeql.controlflow.PrintGraph as PrintGraph + +private module PrintGraphInput implements PrintGraph::InputSig { + private import java as J + + class Callable = J::Callable; + + class ControlFlowNode = J::ControlFlowNode; + + ControlFlowNode getASuccessor(ControlFlowNode n, SuccessorType t) { result = n.getASuccessor(t) } +} + +import PrintGraph::PrintGraph From 83adf793e4bca2a0673dd0a50a67c6bd8fda3621 Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Wed, 4 Feb 2026 15:28:37 +0100 Subject: [PATCH 3/4] Cfg: Fix compilation. --- shared/controlflow/codeql/controlflow/Cfg.qll | 7 ++++--- shared/controlflow/codeql/controlflow/PrintGraph.qll | 6 +++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/shared/controlflow/codeql/controlflow/Cfg.qll b/shared/controlflow/codeql/controlflow/Cfg.qll index 6f9a3adfec46..157bf0ffd4f3 100644 --- a/shared/controlflow/codeql/controlflow/Cfg.qll +++ b/shared/controlflow/codeql/controlflow/Cfg.qll @@ -1122,6 +1122,9 @@ module MakeWithSplitting< /** Gets the scope of this node. */ CfgScope getScope() { result = getNodeCfgScope(this) } + /** Gets the enclosing callable of this node. */ + CfgScope getEnclosingCallable() { result = this.getScope() } + /** Gets a successor node of a given type, if any. */ Node getASuccessor(SuccessorType t) { result = getASuccessor(this, t) } @@ -1315,9 +1318,7 @@ module MakeWithSplitting< private module PrintGraphInput implements Pp::InputSig { class Callable = CfgScope; - class ControlFlowNode extends Node { - Callable getEnclosingCallable() { result = this.getScope() } - } + class ControlFlowNode = Node; ControlFlowNode getASuccessor(ControlFlowNode n, SuccessorType t) { result = n.getASuccessor(t) diff --git a/shared/controlflow/codeql/controlflow/PrintGraph.qll b/shared/controlflow/codeql/controlflow/PrintGraph.qll index c09aeeff71cb..635b671888f6 100644 --- a/shared/controlflow/codeql/controlflow/PrintGraph.qll +++ b/shared/controlflow/codeql/controlflow/PrintGraph.qll @@ -102,13 +102,13 @@ module PrintGraph Input> { /** Provides the input to `ViewCfgQuery`. */ signature module ViewCfgQueryInputSig { - /** The source file selected in the IDE. Should be an `external` predicate. */ + /** Gets the source file selected in the IDE. Should be an `external` predicate. */ string selectedSourceFile(); - /** The source line selected in the IDE. Should be an `external` predicate. */ + /** Gets the source line selected in the IDE. Should be an `external` predicate. */ int selectedSourceLine(); - /** The source column selected in the IDE. Should be an `external` predicate. */ + /** Gets the source column selected in the IDE. Should be an `external` predicate. */ int selectedSourceColumn(); /** From 81977f11a10da519b1fb2adc1342690b585b8b91 Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Thu, 5 Feb 2026 08:59:28 +0100 Subject: [PATCH 4/4] Cfg: qldoc + overlay fixups. --- shared/controlflow/codeql/controlflow/PrintGraph.qll | 3 +++ 1 file changed, 3 insertions(+) diff --git a/shared/controlflow/codeql/controlflow/PrintGraph.qll b/shared/controlflow/codeql/controlflow/PrintGraph.qll index 635b671888f6..c4a942feab23 100644 --- a/shared/controlflow/codeql/controlflow/PrintGraph.qll +++ b/shared/controlflow/codeql/controlflow/PrintGraph.qll @@ -3,6 +3,8 @@ * CFG" query. Also provides modules for printing control flow graphs in tests * and as Mermaid diagrams. */ +overlay[local?] +module; private import codeql.util.FileSystem private import codeql.util.Location @@ -22,6 +24,7 @@ signature module InputSig { ControlFlowNode getASuccessor(ControlFlowNode n, SuccessorType t); } +/** Provides modules for printing control flow graphs. */ module PrintGraph Input> { private import Input