Unified: Add control flow graph. - #22479
Conversation
| AstNode getUpdate(int index) { none() } | ||
| } | ||
|
|
||
| class ForeachStmt extends LoopStmt instanceof U::ForEachStmt { |
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Foreach, guard-statement, and closure-capture paths currently produce incorrect or unreachable control flow.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review tier: Balanced
Findings: 1
New issues introduced by this change (2)
| Severity | Finding |
|---|---|
unified/ql/lib/codeql/unified/internal/ControlFlowGraph.qll — There are no edge-expectation tests for this new CFG implementation. The consistency query catches… |
|
unified/ql/lib/codeql/unified/internal/ControlFlowGraph.qll — Excluding every child of a callable drops closure capture initializers from the CFG. FunctionExpr… |
What changed in this PR
Adds an initial unified-language CFG implementation with IDE visualization and consistency checks.
Changes:
- Adapts unified AST nodes to the shared control-flow framework.
- Exposes CFG APIs and adds the required dependency.
- Adds CFG visualization and consistency queries.
| File | Description |
|---|---|
unified/ql/lib/unified.qll |
Exposes CFG APIs. |
unified/ql/lib/qlpack.yml |
Adds the control-flow dependency. |
unified/ql/lib/ide-contextual-queries/printCfg.ql |
Adds IDE CFG visualization. |
unified/ql/lib/codeql/unified/internal/FacadeAst.qll |
Adds block-last-statement support. |
unified/ql/lib/codeql/unified/internal/ControlFlowGraph.qll |
Implements unified CFG modeling. |
unified/ql/consistency-queries/qlpack.yml |
Defines the consistency-query pack. |
unified/ql/consistency-queries/CfgConsistency.ql |
Enables shared CFG consistency checks. |
Suppressed comments (3)
unified/ql/lib/codeql/unified/internal/ControlFlowGraph.qll:85
GuardIfStmtis currently left to the default child ordering, which evaluates its condition and then itselseblock unconditionally. The Swift extractor emits this node for everyguard … elsestatement, so the CFG incorrectly enters theelseblock even on the true branch. Model it as the sharedIfStmtshape, with nothenbranch, so true flow continues after the guard and false flow enterselse.
class IfStmt extends Stmt {
IfStmt() { none() }
unified/ql/lib/codeql/unified/internal/ControlFlowGraph.qll:132
- This mapping is empty:
getPattern()returnsPattern, while this method requiresExpr(the schema makesexprandpatternseparate alternatives underexpr_or_pattern). Consequently, the shared foreach edge from a non-empty collection togetVariable()has no target, so every non-empty foreach path dead-ends before its body. Generalize the shared foreach-variable API toAstNodeand return the pattern through it.
Expr getVariable() { result = super.getPattern() }
unified/ql/lib/codeql/unified/internal/ControlFlowGraph.qll:129
- The foreach guard is omitted from the explicit loop CFG. Although it remains an AST child, the shared implementation disables default child traversal once
ForeachStmthas explicit steps, so the guard is unreachable and the body is entered without testing it. Extend the shared foreach signature/steps to evaluate the guard each iteration and branch back to the loop header when false.
// TODO support foreach guard
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| private module Cfg0 = Make0<Location, Ast>; | ||
|
|
||
| private module Cfg1 = Make1<Input>; | ||
|
|
||
| private module Cfg2 = Make2<Input>; |
| result.getParent() = n and | ||
| result.getParentIndex() = index and | ||
| not n instanceof Callable and | ||
| not skipControlFlow(n) and | ||
| not skipControlFlow(result) |
asgerf
left a comment
There was a problem hiding this comment.
LGTM apart from the public import
Co-authored-by: Asger F <asgerf@github.com>


This is a very rough initial version, but it provides the basic functionality including "View CFG" support and consistency queries.