Skip to content

Experiment: restore captured-jump selectivity with shared SSA - #22402

Draft
yoff wants to merge 5 commits into
yoff/python-shared-cfg-dataflow-flipfrom
yoff-captured-jump-selectivity
Draft

yoff wants to merge 5 commits into
yoff/python-shared-cfg-dataflow-flipfrom
yoff-captured-jump-selectivity

Conversation

@yoff

@yoff yoff commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Experiment

Dependent on #21925. This draft does not claim semantic equivalence with legacy ESSA; it measures the closest general shared-SSA gate for TypeTrackingInput::capturedJumpStep.

The gate now requires an actual non-phi SsaImpl::EssaNodeDefinition whose getSourceVariable() is the captured SsaSourceVariable and whose getDefiningNode() is the candidate Cfg::DefinitionNode, instead of admitting every syntactic Variable.getAStore().

Exact two-commit structure

  1. 439edf66b1fcc4b6c73827f1bad0e9d2716db4e8 — red-state inline regression test; broad behavior passes with the safe read marked SPURIOUS.
  2. 33133d6cc5e8cd179d7787454140da64c46220e3 — selective shared-SSA implementation; expected lost captures become MISSING and the safe-read spurious results disappear.

Tuple measurements

Database Broad SSA-gated Broad-only SSA-only
Focused typetracking test DB 49 48 1 0
zauberzeug/nicegui@26756ccf local corpus DB 6,958 6,929 29 0
openai/codex@cd2d84d4 local corpus DB 154 150 4 0
qlustered/deepdiff@6f3d5eeb local corpus DB 2,146 2,140 6 0

No reusable exact Airflow a9da0f7fb48dc7526b2745be3e8fe64e1c775da2 database was present in the current worktree, session artifacts, or known local CodeQL database/cache locations, so this experiment does not fabricate an Airflow measurement.

Precision trade-off

The focused test assigns tracked, reads it through a closure, reassigns the variable to a safe value, then reads through the closure again. The broad gate reports both reads. The SSA gate removes the one broad-only write tuple: three true-positive annotation sites before reassignment are intentionally recorded as MISSING, while two SPURIOUS annotation sites for the safe sibling read disappear. This is evidence of restored legacy-style selectivity, not proof of semantic equivalence.

Validation

  • codeql test run python/ql/test/library-tests/dataflow/typetracking --threads=2 — 2/2 passed in both red and green states.
  • codeql test run python/ql/test/library-tests/dataflow/typetracking python/ql/test/library-tests/dataflow/global-or-captured-vars python/ql/test/library-tests/dataflow/variable-capture python/ql/test/library-tests/dataflow/regression --threads=2 — 7/7 passed.
  • codeql query format --check-only python/ql/lib/semmle/python/dataflow/new/internal/TypeTrackingImpl.qll — passed.
  • Temporary tuple instrumentation compiled and produced the exact counts above; it is not part of the branch.

The branch is ready for a DCA comparison against #21925 (including baseline #38534 / data branch data/yoff/PR-21925-0-python__1).

yoff and others added 5 commits August 20, 2026 10:32
Flips the Python dataflow trunk from the legacy CFG (semmle/python/Flow.qll)
and legacy ESSA SSA (semmle/python/essa/*) to the new shared CFG facade
(semmle.python.controlflow.internal.Cfg) and the new SSA adapter
(semmle.python.dataflow.new.internal.SsaImpl), both introduced
additively in the preceding PRs in this stack.

This is the trunk-flip equivalent of the original draft PR #21894 (kept
around as documentation), rebased on top of the four preparatory PRs:

  P1: Remove AstNode.getAFlowNode() and rewrite callers (#21919).
  P2: Qualify Flow.qll's AST references with Py:: prefix (#21920).
  P3: Add new shared-CFG-backed control flow graph (#21921).
  P4: Add new shared-SSA-backed SSA adapter (#21923).

The Python dataflow library (semmle/python/dataflow/new/) now imports
the new CFG facade and SSA adapter. All CFG-typed predicates
(ControlFlowNode, CallNode, BasicBlock, NameNode, AttrNode, ...) are
qualified with the Cfg:: prefix; SSA references switch from
EssaVariable/EssaDefinition to SsaImpl::Definition/SourceVariable.

GuardNode is redesigned to use the new CFG's outcome-node model
(isAfterTrue / isAfterFalse) instead of the legacy ConditionBlock +
flipped indirection. Only BarrierGuard<...> is preserved as public
API.

Framework files (Bottle, FastApi, Django, Tornado, Pyramid, Stdlib,
...) are updated to take CFG nodes from the new facade.

A handful of dataflow consistency tweaks for the new CFG:
- Augmented-assignment targets are treated as both load and store.
- 'from X import *' produces uncertain SSA writes for unknown names.
- CFG nodes are canonicalised so dataflow does not see equivalent
  pre/post-order pairs as distinct nodes.

Two AST tweaks for the new CFG:
- AstNodeImpl: omit PEP 695 type-parameter names from
  FunctionDefExpr / ClassDefExpr children.
- ImportResolution: drop the legacy essa import.

Test churn (~175 files): reblessed library- and query-test .expected
files reflect slightly different CFG granularity, different toString
output, and a handful of true alert deltas in security queries.

Verification: all 367 lib + src + consistency-queries compile clean.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The `Cfg::ControlFlowNode` facade re-exports the shared CFG library's
`dominates`/`strictlyDominates` predicates, which are declared
`bindingset[this, that]` + `pragma[inline_late]` and are meant to be used
as bound-pair membership checks. The facade wrappers dropped these
annotations (using plain `pragma[inline]`), so even though the only
callers — the `with` / `async with` taint steps in DataFlowPrivate.qll
and TaintTrackingPrivate.qll — bind both endpoints, the optimizer was
free to materialise `Cfg::ControlFlowNode.strictlyDominates/1` as a full
O(nodes^2) relation over the (larger) shared-CFG node set.

On some projects this dominated analysis time entirely (DCA showed e.g.
ICTU/quality-time and biosimulations regressing ~75-160x). Restoring
`bindingset[this, other]` + `pragma[inline_late]` on the wrappers turns
the predicate back into a bound-pair check and is result-preserving (only
binding annotations change, the predicate body is unchanged).

Reproduced on ICTU/quality-time: full python-security-extended suite went
from stalling >20min on `strictlyDominates` to completing in ~6min; all
ControlFlow and dataflow/coverage library tests pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Document the public expression adapter and apply the canonical QL annotation ordering required by the formatter.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 529363f5-bc7d-4f0b-9f47-e03ba9aa0cdf
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant