Skip to content

SSA: Tolerate multiple variables being read at the same CFG node - #22744

Open
asgerf wants to merge 17 commits into
github:mainfrom
asgerf:ssa/multiple-reads-v2
Open

asgerf wants to merge 17 commits into
github:mainfrom
asgerf:ssa/multiple-reads-v2

Conversation

@asgerf

@asgerf asgerf commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Fixes an issue with the SSA data-flow integration library that occurs when multiple variables are read at the same CFG node.

This situation happens when it is instantiated from the VariableCapture library. It also happens in unified due to how we use synthetic reads to mark post-update positions.

The assumption has a subtle presence in this bit of code:

    private class ReadNodeImpl extends ExprNodeImpl {
      private BasicBlock bb_;
      private int i_;

      ReadNodeImpl() {
        variableRead(bb_, i_, v_, true) and
        this.getExpr().hasCfgNode(bb_, i_)
      }

Since this is only bound to the expression, the variableRead join can fan out to multiple variables when not uniquely determined by the CFG node. Even if Expr is constructed to be unique to the variable, the correlation is forgotten in this join and they get mixed up anyway. The fix was to make TExprNode unique to a specific variable.

The real culprit is the Expr class from the data-flow integration input. I propose we remove Expr entirely and replace ExprNode with ReadNode: a canonical representative for a (bb,i,v) triple from the variableRead input, and likewise for PostUpdateNode. But such a change is too large for this PR as it requires language-specific refactorings. But the fix is essentially a precursor to this solution: when Expr is a CFG node, ReadNode is effectively a (bb,i,v) triple as it rightfully should be.

Comment on lines +2150 to +2152
/**
* The given `read` reads multiple variables at once. `var` is bound to one of them.
*/
asgerf added 5 commits October 2, 2026 16:22
These consistency violations originate from the VariableCapture instantation
in C#, JS, Python, and Ruby.
The previously-added consistency errors are gone.
Unified also had consistency errors from its LocalSSA instantiation, due
to its use of synthetic read nodes to represent post-update positions.
Many variables can have a post-update at the same CFG node.
@asgerf
asgerf force-pushed the ssa/multiple-reads-v2 branch from bfd363b to 1bbd5d5 Compare October 2, 2026 14:23
asgerf added 3 commits October 2, 2026 16:47
Many tests passed for the wrong reasons, due to the SSA bug.

We need more library/operator modelling to actually find these flows.
Switched to TaintTracking and adds some very ad-hoc steps to recover
most of the results.

Some more tests pass and others fail; these are now consistent with what
we actually model.
@asgerf
asgerf force-pushed the ssa/multiple-reads-v2 branch 2 times, most recently from 1bbd5d5 to 2c637ab Compare October 2, 2026 14:55
@asgerf asgerf changed the title Ssa/multiple reads v2 SSA: Tolerate multiple variables being read at the same CFG node Oct 2, 2026
The C++ instantiation of DataFlowIntegration generated a bad join order
@asgerf
asgerf force-pushed the ssa/multiple-reads-v2 branch from 4048c51 to cbd6d35 Compare October 5, 2026 07:39
@asgerf
asgerf requested a balanced review from Copilot October 5, 2026 12:07

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.

Copilot review overview

🟡 Changes recommended

Variable correlation remains incomplete in post-update, must-flow, and barrier joins, and the new Swift URL rule overmatches user-defined calls.

Review effort: Balanced
Findings: 1 High severity · 1 Medium severity

Open (2)
What changed in this PR

This PR makes SSA data-flow nodes variable-specific when multiple variables share one CFG node and adds consistency coverage.

Changes:

  • Adds variable identity to SSA expression nodes and ambiguity checks.
  • Adds Swift regression tests and updates path-injection taint flow.
  • Enables the new consistency check across supported languages.
File Description
shared/​ssa/​codeql/​ssa/​Ssa.qll Qualifies expression nodes by variable and adds consistency checks.
shared/​dataflow/​codeql/​dataflow/​VariableCapture.qll Exposes SSA ambiguity checks for variable capture.
unified/​ql/​consistency-queries/​LocalSsaConsistency.ql Enables the new unified consistency check.
unified/​ql/​lib/​codeql/​unified/​internal/​dataflow/​DataFlowPluginSwift.qll Adds Swift-specific taint steps.
unified/​ql/​src/​queries/​security/​CWE-022/​PathInjection.ql Switches path injection to taint tracking.
unified/​ql/​test/​library-tests/​dataflow/​test.swift Adds a shared-CFG-node regression case.
unified/​ql/​test/​library-tests/​dataflow/​test.expected Updates generated data-flow expectations.
unified/​ql/​test/​query-tests/​security/​CWE-022/​PathInjection/​testPathInjection.swift Updates path-injection annotations.
unified/​ql/​test/​query-tests/​security/​CWE-022/​PathInjection/​PathInjectionTest.expected Updates generated path-injection results.
rust/​ql/​consistency-queries/​SsaConsistency.ql Enables SSA ambiguity checking for Rust.
ruby/​ql/​consistency-queries/​SsaConsistency.ql Enables SSA ambiguity checking for Ruby.
java/​ql/​consistency-queries/​SsaConsistency.ql Enables SSA ambiguity checking for Java.
csharp/​ql/​consistency-queries/​SsaConsistency.ql Enables SSA ambiguity checking for C#.
python/​ql/​test/​library-tests/​dataflow/​variable-capture/​dataflow-capture-consistency.expected Updates generated consistency expectations.
javascript/​ql/​test/​library-tests/​FlowSummary/​CaptureConsistency.expected Updates generated consistency expectations.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +1683 to +1687
TExprNode(DfInput::Expr e, SourceVariable v, Boolean isPost) {
exists(Definition def |
def.getSourceVariable() = v and
e = DfInput::getARead(def)
)
Comment on lines +38 to +42
exists(CallExpr call |
call.getCallee().(Identifier).getValue() = ["URL", "NSURL"] and
node1.isResultValue(call.getNamedArgument("string")) and
step.taint() and
node2.isResultValue(call)
@asgerf
asgerf marked this pull request as ready for review October 5, 2026 12:52
@asgerf
asgerf requested review from a team as code owners October 5, 2026 12:52
@asgerf
asgerf requested a review from aschackmull October 5, 2026 12:52
@asgerf asgerf added the no-change-note-required This PR does not need a change note label Oct 5, 2026
@asgerf
asgerf force-pushed the ssa/multiple-reads-v2 branch from fab449a to 32e5525 Compare October 5, 2026 13:02
Comment thread shared/ssa/codeql/ssa/Ssa.qll Outdated
/** Gets the underlying expression. */
DfInput::Expr getExpr() { result = e }

/** Holds if represents the access to `var` performed at `expr`. */

@hvitved hvitved Oct 5, 2026 •

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.

grammar

aschackmull
aschackmull previously approved these changes Oct 6, 2026

@aschackmull aschackmull 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.

LGTM! Thanks for fixing this.

C++ has a subtle dependency on this behavior.

Previously, when a single TExprNode corresponded to multiple variables,
it would be materialised if just one of those variables had a reaching
definition.

Now the node has been split up, which generally works fine for C++, but
in some cases one of the split-off nodes is missing its reaching def,
but C++ still needs to find the flow to its next use, and therefore
needs the node to be materialised.
@asgerf

asgerf commented Oct 8, 2026

Copy link
Copy Markdown
Contributor Author

I decided to investigate the rare loss of a source-sink pair reported by the C++ analysis, and tracked the discrepancy down to the fix described in fc2d9f3. Will run another rounds of DCA.

I also plan a small follow-up PR with some simplifications to the C++ SSA library that are made possible by this change (it currently goes a long way to work around the bug fixed in this PR).

@asgerf
asgerf requested a review from a team as a code owner October 8, 2026 08:29
@asgerf
asgerf marked this pull request as draft October 8, 2026 08:29
@asgerf

asgerf commented Oct 8, 2026

Copy link
Copy Markdown
Contributor Author

Taking back into draft until unit tests and DCA runs are happy

variableRead(bb, i, v, true) and
// Only materialise if 'expr' has a reaching definition.
// Note that the read may correspond to a different variable than 'v', but the C++
// instantiation currently expects this particular behaviour.
@github-actions github-actions Bot added the C++ label Oct 8, 2026
@asgerf

asgerf commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor Author
Rerun has been triggered: 2 restarted 🚀

@asgerf
asgerf marked this pull request as ready for review October 8, 2026 12:39
@asgerf

asgerf commented Oct 8, 2026

Copy link
Copy Markdown
Contributor Author

PTAL. I believe I've reduced the impact for C++ without negatively affecting any other languages.

This branch has not been deployed

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

Labels

C# C++ DataFlow Library Java JS no-change-note-required This PR does not need a change note Python Ruby Rust Pull requests that update Rust code Unified

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants