Skip to content

Go: use shared SSA library (codeql.ssa.Ssa)#21554

Open
Copilot wants to merge 6 commits into
mainfrom
copilot/make-go-use-ssa-library
Open

Go: use shared SSA library (codeql.ssa.Ssa)#21554
Copilot wants to merge 6 commits into
mainfrom
copilot/make-go-use-ssa-library

Conversation

Copilot AI commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

Go was the last language with its own bespoke SSA implementation. This migrates it to the shared codeql.ssa.Ssa library, consistent with C#, Java, Ruby, Rust, and Swift.

Key changes

  • go/ql/lib/qlpack.yml: Add codeql/ssa: ${workspace} dependency.

  • BasicBlocks.qll: Add module Cfg implements BB::CfgSig<Location> — the CFG adapter required by the shared library.

  • SsaImpl.qll (complete rewrite):

    • Keeps private Go-specific predicates (defAt, useAt, mayCapture, assignedThroughClosure).
    • Implements SsaInput implements SsaImplCommon::InputSig<Location, BasicBlock> feeding variableWrite (certain = explicit def, uncertain = capture) and variableRead.
    • Adds a synthetic uncertain read at function-exit for captured variables in every function that writes the variable (the declaring function or any closure scope). This preserves the old v.isCaptured() liveness shortcut for the last write reaching the exit of each such function.
    • Instantiates via SsaImplCommon::Make<Location, BasicBlocks::Cfg, SsaInput> as Impl.
    • Exports type aliases (Definition, WriteDefinition, UncertainWriteDefinition, PhiNode) and cached predicates (ssaDefReachesRead, phiHasInputFromBlock, firstUse, adjacentUseUse).
  • SSA.qll (updated public API):

    • SsaVariable, SsaDefinition now extend the shared Definition instead of a local newtype.
    • SsaExplicitDefinition extends SsaDefinition, WriteDefinition
    • SsaVariableCapture extends SsaImplicitDefinition, UncertainWriteDefinition
    • SsaPhiNode extends SsaPseudoDefinition, PhiNode
    • Removed the definesAt(bb, idx, v) compatibility wrapper (no external callers) and the now-redundant getBasicBlock() override (it's final in the shared library).

toString() format change

The shared library's default formats are used: "SSA def(x)" for explicit/capture definitions and "SSA phi(x)" for phi nodes (replacing Go's previous "definition of x", "capture variable x", and "x = phi(def@L:C, ...)"). The preparatory commit "Preemptively change toString() for SSA classes" regenerated all affected .expected files to use the new formats before the migration, so per-test diffs are limited to format updates.

Note on the test diff in the last commit

The last commit ("Test changes to investigate") removes two GVN entries for test3() in GlobalValueNumbering/GlobalValueNumber.expected:

func test3() (res int) {
    res = 7              // L34
    defer func() { res = 8 }()
    return 9             // L38 (implicitly: res = 9; return)
}
  • main.go:33:15:33:17 | SSA def(res) | main.go:10:7:10:7 | 0 — this write is dead because it is overwritten by res = 7 on the line below. Previously all SsaNodes for captured variables were never pruned for liveness. Note that the line | main.go:33:15:33:17 | zero value for res | main.go:10:7:10:7 | 0 | at the same location is kept because it corresponds to an InstructionNode instead of an SsaNode, and they are not pruned based on liveness.
  • main.go:34:2:34:4 | SSA def(res) | main.go:34:8:34:8 | 7 — the write res = 7 is dead: it is overwritten by the implicit res = 9 from return 9 before any local read, and the deferred closure body only runs after return 9, so the value 7 is never observed anywhere. The old SSA kept this def alive solely because of the conservative v.isCaptured() shortcut. The shared library uniformly prunes truly dead writes — note that test2.res = 5 is already pruned even on the old SSA, because res there is not captured. The new behaviour is therefore more uniform and slightly more precise; no downstream query result changes were observed.

This is consistent with how the shared SSA library is used in other languages (C#, Java, Ruby, Rust, Swift) and is not a regression worth working around.

Original prompt

cpp, csharp, java, ruby, rust and swift all use the shared SSA library. You can see this by searching for files containing import codeql.ssa.Ssa. Make go use it to.

Created from VS Code.


📍 Connect Copilot coding agent with Jira, Azure Boards or Linear to delegate work to Copilot in one click without leaving your project management tool.

Copilot AI changed the title [WIP] Add shared SSA library integration for Go Go: use shared SSA library (codeql.ssa.Ssa) Mar 23, 2026
Copilot AI requested a review from owen-mc March 23, 2026 17:21
class EntryBasicBlock = BbImpl::EntryBasicBlock;

/** Provides a `CfgSig` view of Go's control-flow graph for use with the shared SSA library. */
module Cfg implements BB::CfgSig<Location> {
Comment thread go/ql/lib/semmle/go/dataflow/SsaImpl.qll Fixed
@owen-mc owen-mc force-pushed the copilot/make-go-use-ssa-library branch 2 times, most recently from c83c654 to a899767 Compare March 25, 2026 19:02
@owen-mc

owen-mc commented Mar 25, 2026

Copy link
Copy Markdown
Contributor

@copilot The last commit contains test changes which need investigation. There are some tests that show we have lost some results for SSA definitions of capture variables. And there are some query tests which show lost results, which I believe are because of capture variables as well. Please investigate and fix the SSA implemention so that the query tests pass. It is okay if there are test differences relating to the renaming of data flow nodes or SSA classes.

Copilot AI commented Mar 25, 2026

Copy link
Copy Markdown
Contributor Author

@copilot The last commit contains test changes which need investigation. There are some tests that show we have lost some results for SSA definitions of capture variables. And there ar...

Fixed in 5a786ac. Two issues were causing the lost results:

  1. Captured variable liveness in closures: The old SSA had (liveAfterDef(bb, i, v) or v.isCaptured()) which kept any write to a captured variable alive regardless of local liveness. My implementation only added a synthetic uncertain read at the exit of the declaring function, but not at the exit of closures that write the captured variable. Writes like x++ inside a closure had no subsequent read in the closure scope, so the shared library's liveness pruning removed them. Fixed by extending the synthetic uncertain read to the exit of every function that writes a captured variable.

  2. toString formats: The toString() overrides for SsaExplicitDefinition, SsaVariableCapture, SsaPhiNode, and SsaVariable were commented out in the "Fix compiler errors" commit, causing the shared library's default "SSA def(x)" format instead of Go's "definition of x". Re-enabled all the overrides.

@owen-mc owen-mc force-pushed the copilot/make-go-use-ssa-library branch 2 times, most recently from 286611e to 1cb1d47 Compare June 9, 2026 06:01
@owen-mc owen-mc marked this pull request as ready for review June 9, 2026 11:30
@owen-mc owen-mc requested a review from a team as a code owner June 9, 2026 11:30
Copilot AI review requested due to automatic review settings June 9, 2026 11:30
@owen-mc

owen-mc commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

@copilot Please update the PR description and then investigate the change in test output in the last commit and explain why it happens and whether it is a sign that something should be fixed.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Migrates Go’s SSA implementation from a bespoke library to the shared codeql.ssa.Ssa library, aligning Go with other CodeQL languages and updating the Go pack’s SSA adapters, public SSA API, and test baselines accordingly.

Changes:

  • Added the codeql/ssa dependency and introduced a Go CFG adapter (BasicBlocks::Cfg) for the shared SSA library.
  • Rewrote Go’s SSA implementation to use codeql.ssa.Ssa and updated the public Go SSA API wrappers.
  • Updated numerous Go query/library test expectation files and added new SSA closure test cases.
Show a summary per file
File Description
go/ql/test/query-tests/Security/CWE-601/BadRedirectCheck/BadRedirectCheck.expected Updates expected dataflow/SSA node labels to the new SSA definition formatting.
go/ql/test/query-tests/Security/CWE-347/MissingJwtSignatureCheck.expected Updates expected edges/nodes to reference SSA definitions using the new formatting.
go/ql/test/query-tests/Security/CWE-326/InsufficientKeySize.expected Refreshes expected SSA definition labels.
go/ql/test/query-tests/Security/CWE-322/InsecureHostKeyCallback.expected Refreshes expected SSA/capture-related labels in provenance edges.
go/ql/test/query-tests/Security/CWE-312/CleartextLogging.expected Large baseline update reflecting new SSA definition label formatting across many paths.
go/ql/test/query-tests/Security/CWE-079/StoredXss.expected Updates expected SSA definition labels.
go/ql/test/query-tests/Security/CWE-078/CommandInjection.expected Updates expected SSA definition labels in edges/nodes.
go/ql/test/query-tests/Security/CWE-022/ZipSlip.expected Updates expected SSA definition labels.
go/ql/test/query-tests/Security/CWE-022/UnsafeUnzipSymlink.expected Updates expected SSA definition labels.
go/ql/test/query-tests/InconsistentCode/UnhandledCloseWritableHandle/UnhandledCloseWritableHandle.expected Updates expected SSA/capture provenance labels.
go/ql/test/query-tests/InconsistentCode/MissingErrorCheck/MissingErrorCheck.expected Updates expected SSA definition labels for result/err.
go/ql/test/library-tests/semmle/go/frameworks/Yaml/tests.expected Records model-test failures related to SSA definition label formatting (should be resolved).
go/ql/test/library-tests/semmle/go/frameworks/Twirp/RequestForgery.expected Updates expected SSA/capture-related labels and return-node labels.
go/ql/test/library-tests/semmle/go/frameworks/gqlgen/gqlgen.expected Records model-test failures related to SSA definition label formatting (should be resolved).
go/ql/test/library-tests/semmle/go/frameworks/GoMicro/LogInjection.expected Updates expected SSA definition labels for request-derived values.
go/ql/test/library-tests/semmle/go/frameworks/GoMicro/gomicro.expected Records model-test failures related to SSA definition label formatting (should be resolved).
go/ql/test/library-tests/semmle/go/frameworks/GoKit/RemoteFlowSources.expected Records model-test failures related to SSA definition label formatting (should be resolved).
go/ql/test/library-tests/semmle/go/frameworks/Beego/CleartextLogging.expected Updates expected SSA definition labels in taint flow baselines.
go/ql/test/library-tests/semmle/go/dataflow/SSA/VarUses.expected Updates SSA-related expected outputs for variable uses (including closure cases).
go/ql/test/library-tests/semmle/go/dataflow/SSA/VarDefs.expected Updates SSA-related expected outputs for variable definitions (including closure cases).
go/ql/test/library-tests/semmle/go/dataflow/SSA/SsaWithFields.expected Updates SSA-with-fields baseline to new “SSA def/phi” formatting.
go/ql/test/library-tests/semmle/go/dataflow/SSA/SsaDefinition.expected Updates SSA definition rendering baseline.
go/ql/test/library-tests/semmle/go/dataflow/SSA/main.go Adds new closure read/update cases to exercise SSA liveness/capture behavior.
go/ql/test/library-tests/semmle/go/dataflow/SSA/DefUse.expected Updates def-use baseline to the new SSA definition/phi formatting.
go/ql/test/library-tests/semmle/go/dataflow/PromotedFields/LocalFlowStep.expected Updates baseline SSA definition labels for promoted-field dataflow steps.
go/ql/test/library-tests/semmle/go/dataflow/GlobalValueNumbering/GlobalValueNumber.expected Updates baseline SSA definition labels for GVN-related steps.
go/ql/test/library-tests/semmle/go/dataflow/FunctionInputsAndOutputs/FunctionInput_getExitNode.expected Updates baseline SSA labels for parameters/receivers at function exits.
go/ql/test/library-tests/semmle/go/dataflow/FunctionInputsAndOutputs/FunctionInput_getEntryNode.expected Updates baseline SSA labels for results/locals at function entry.
go/ql/test/library-tests/semmle/go/dataflow/FlowSteps/LocalFlowStep.expected Updates baseline SSA labels used in local flow-step testing.
go/ql/test/library-tests/semmle/go/dataflow/ExternalValueFlow/srcs.expected Updates baseline SSA labels for external value-flow sources.
go/ql/test/library-tests/semmle/go/dataflow/ExternalTaintFlow/srcs.expected Updates baseline SSA labels for external taint-flow sources.
go/ql/test/library-tests/semmle/go/concepts/Regexp/RegexpPattern.expected Updates baseline SSA definition label for regexp pattern definition.
go/ql/test/experimental/Unsafe/WrongUsageOfUnsafe.expected Updates baseline SSA labels for unsafe usage provenance.
go/ql/test/example-tests/snippets/typeinfo.expected Updates snippet baseline SSA labels for req definition.
go/ql/lib/semmle/go/dataflow/SsaImpl.qll Replaces Go’s bespoke SSA impl with a shared-SSA-based adapter and exported predicates/types.
go/ql/lib/semmle/go/dataflow/SSA.qll Updates public SSA API wrapper classes to extend shared SSA definitions and adjusts labeling behavior.
go/ql/lib/semmle/go/controlflow/BasicBlocks.qll Adds a CfgSig-compatible CFG view module for shared SSA integration.
go/ql/lib/qlpack.yml Adds codeql/ssa: ${workspace} dependency to the Go library pack.
go/ql/lib/change-notes/2026-03-23-use-shared-ssa.md Adds a Go pack change note documenting the SSA migration and potential edge-case differences.

Copilot's findings

  • Files reviewed: 40/40 changed files
  • Comments generated: 5

Comment thread go/ql/test/library-tests/semmle/go/frameworks/Yaml/tests.expected Outdated
Comment thread go/ql/test/library-tests/semmle/go/frameworks/gqlgen/gqlgen.expected Outdated
Comment thread go/ql/test/library-tests/semmle/go/frameworks/GoMicro/gomicro.expected Outdated
Comment thread go/ql/test/library-tests/semmle/go/frameworks/GoKit/RemoteFlowSources.expected Outdated
Copilot stopped work on behalf of owen-mc due to an error June 9, 2026 11:40
owen-mc and others added 2 commits June 9, 2026 13:52
- Extend synthetic uncertain reads to function exits of any function
  that writes a captured variable, not just the declaring function.
  This ensures writes to captured variables inside closures remain
  live (matching the old `v.isCaptured()` liveness shortcut).
- Uncomment toString overrides for SsaExplicitDefinition, SsaVariableCapture,
  SsaPhiNode, and SsaVariable to restore original output formats.
- Revert test expected files to pre-test-changes state matching the
  correct toString formats and capture variable results.

Agent-Logs-Url: https://github.com/github/codeql/sessions/6dbf9d42-b2e2-42a2-984b-8ea31df4e633

Co-authored-by: owen-mc <62447351+owen-mc@users.noreply.github.com>
@owen-mc owen-mc force-pushed the copilot/make-go-use-ssa-library branch from 1cb1d47 to c4b1ff2 Compare June 9, 2026 11:53
@owen-mc

owen-mc commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

@copilot Please update the PR description and then investigate the change in test output in the last commit and explain why it happens and whether it is a sign that something should be fixed. If it is reasonably clear that it should be fixed and how to fix it then go ahead and fix it.

Copilot AI commented Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

@copilot Please update the PR description and then investigate the change in test output in the last commit and explain why it happens and whether it is a sign that something should b...

Updated the PR description.

Investigation of the last commit's test diff

The two removed entries in GlobalValueNumbering/GlobalValueNumber.expected are both for test3():

func test3() (res int) {
    res = 7              // L34
    defer func() { res = 8 }()
    return 9             // L38 (implicitly: res = 9; return)
}

main.go:33:15:33:17 | SSA def(res) | main.go:10:7:10:7 | 0 — duplicate cleanup. The kept entry main.go:33:15:33:17 | zero value for res | main.go:10:7:10:7 | 0 already represents the same zero‑init of the named return at the same location, so the GVN equivalence to literal 0 is preserved through the zero value for res IR instruction.

main.go:34:2:34:4 | SSA def(res) | main.go:34:8:34:8 | 7res = 7 is genuinely dead in this program: it is overwritten by the implicit res = 9 from return 9 before any local read, and the deferred closure body runs only after return 9 completes, so 7 is never observed anywhere. The old SSA preserved this def only because of the blanket v.isCaptured() liveness shortcut. The shared library prunes truly dead writes uniformly — the analogous write in test2() (res = 5 overwritten by return 6) is already pruned on both the old and new SSA, the only difference being that res there is not captured.

So the change in behaviour is: dead writes to captured variables are now pruned the same way as dead writes to non‑captured variables. I think this is an acceptable precision improvement rather than something to fix:

  • It's uniform — captured vs. non‑captured no longer behave differently for purely dead writes.
  • Matches the design of the shared SSA library (used by C#, Java, Ruby, Rust, Swift).
  • The lost def is unreachable from any subsequent use (the closure body executes after return 9), so no real dataflow or query result is lost.
  • The res = 8 write inside the closure and the res = 9 from return 9 are both still tracked (kept alive by the synthetic exit reads added in 5e0b5a7).
  • Reproducing the old behaviour would require adding synthetic uncertain reads between consecutive writes to captured variables (à la Ruby's capturedCallRead) or after every write — non‑trivial, and only changes results for writes that have no observable reads at all.

I therefore haven't reverted the diff. Happy to revisit if you'd like the old conservative behaviour preserved exactly.

@owen-mc

owen-mc commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

DCA looks fine. I think we should run QA, but after this has been reviewed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants