Skip to content

Commit ac7acb3

Browse files
committed
docs(dataflow): analysis levels, Architecture & Tooling, schema decisions
README gains the level table, the locked level-3 substrate decisions (CFG from stdlib ast, hand-built reaching defs, type-based may-alias MVP, documented unsoundness), a level-3 usage example, and a regenerated --help block; CHANGELOG Unreleased entry; level-3 schema decision log tracked at .claude/SCHEMA_DECISIONS.md as SDK-model input (un-ignored past the global .claude exclude). (#67)
1 parent 3e82256 commit ac7acb3

4 files changed

Lines changed: 334 additions & 246 deletions

File tree

.claude/SCHEMA_DECISIONS.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Schema decisions — codeanalyzer-python
2+
3+
Decision log for schema-affecting choices, kept as input for the CLDK SDK
4+
model work (the frontend skill encodes these as shared Pydantic models). The
5+
level-1/2 schema (`PyApplication`, symbol table, call graph) predates this
6+
log; entries below start at level 3.
7+
8+
## Level 3 — `program_graphs` (issue #67, schema_version 1.0.0)
9+
10+
Contract baseline: the CLDK dataflow-graphs contract (shared node kinds, edge
11+
types, JSON shapes; `(signature, node_id)` identity; `CFG_NEXT`/`CDG`/`DDG`/
12+
`CALL`/`PARAM_IN`/`PARAM_OUT`/`SUMMARY` vocabulary). Divergences and
13+
additions, all additive:
14+
15+
1. **Parameter nodes are first-class and live in a per-function
16+
`param_nodes` list**, not inside `cfg.nodes`. The contract's CFG gate
17+
(single ENTRY/EXIT, every node reachable-from-ENTRY/reaches-EXIT, EXIT =
18+
last CFG id) stays exact over `cfg.nodes`; HRB parameter-passing nodes
19+
(`formal_in`/`formal_out`/`actual_in`/`actual_out`) share the function's
20+
id space with ids allocated after EXIT, and carry `var` (the parameter
21+
name, `<return>`, `<capture>:name`, or `<global>:module::name`) plus
22+
`call_node` (owning callsite statement) for actuals.
23+
2. **SUMMARY edges are emitted in `sdg_edges` with both endpoints in the same
24+
signature** (actual_in → actual_out at one callsite). The contract comment
25+
says "cross-function only"; SUMMARY is inherently intra-function in HRB
26+
form and cannot be typed as a `pdg` edge (`CDG|DDG`), so it rides
27+
`sdg_edges`. CALL/PARAM_IN/PARAM_OUT remain cross-function.
28+
3. **Globals are qualified `module::name`** (double colon keeps the qualifier
29+
out of the field-path grammar `base(.field|[*])*`). Cross-module global
30+
identity holds when access flows through the defining module's functions;
31+
direct `from m import g` rebinding is a documented precision loss.
32+
4. **The return value is the pseudo-path `<return>`**, defined at every
33+
return statement and wired to the `formal_out` of the same name.
34+
5. **Python-specific CFG edge kinds used from the shared vocabulary:**
35+
`yield` (resume successor + abandonment edge to EXIT) and `await_resume`.
36+
No renamed or repurposed kinds; node kinds used: `entry`, `exit`,
37+
`statement`, `branch`, `loop`, `return`, `raise`, `handler`.
38+
6. **Infinite loops get a synthetic `exception` edge header → EXIT** (any
39+
Python loop can exit via an async signal), keeping post-dominance rooted.
40+
7. **Call mutations are suffixed weak defs** (`xs.*`): caller-visible
41+
mutation is distinguishable from local rebinding, which decides
42+
`formal_out` allocation for parameters.
43+
8. **`dfg` has no separate section** (per contract): `--graphs dfg` emits the
44+
PDG with only DDG edges; `sdg` implies the dependence edges it stitches.
45+
9. **Taint (`taint_flows`) is not emitted by this analyzer** — deliberately
46+
deferred to the CLDK SDK, where labeled SDG reachability is shared across
47+
languages; only source/sink/sanitizer model packs are per-language.
48+
49+
## Level-3 CPG (Neo4j) — schema.neo4j.json 1.2.0 (additive)
50+
51+
- New label `CFGNode` (merge key `id` = `<signature>#<node_id>`; props
52+
`kind`, `var`, `call_node`, `start_line`, `end_line`, `_module`). Both CFG
53+
statements and parameter nodes ride this one label, distinguished by
54+
`kind` — the parity clause's label set stays minimal.
55+
- New edge types `HAS_CFG_NODE` (PyCallable → CFGNode), `CFG_NEXT` (prop
56+
`kind`), `CDG`, `DDG` (prop `var`), `PARAM_IN`, `PARAM_OUT`, `SUMMARY`
57+
deliberately **not** `PY_`-prefixed: this vocabulary is the shared
58+
cross-language CPG contract.
59+
- `CALL` SDG edges are not projected: the callable-level `PY_CALLS` twin
60+
already carries calls; callsite-statement granularity is recoverable via
61+
`PY_HAS_CALLSITE`/`PY_RESOLVES_TO`.

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,3 +187,8 @@ node_modules/
187187

188188
# Track this repo's CLAUDE.md even though a global gitignore excludes CLAUDE.md
189189
!CLAUDE.md
190+
191+
# Track the schema decision log (SDK-model input) past a global .claude ignore
192+
!.claude/
193+
.claude/*
194+
!.claude/SCHEMA_DECISIONS.md

CHANGELOG.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,34 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [Unreleased]
9+
10+
### Added
11+
- **`--analysis-level 3`: native dataflow graphs** (#67). Whole-program dependence graphs built
12+
in-process from the stdlib `ast` — per-callable exceptional **CFG**s (statement-level, synthetic
13+
ENTRY/EXIT, first-class exception/yield/await edges), **PDG**s (Ferrante–Ottenstein–Warren
14+
control dependence + reaching-definitions data dependence over k-limited access paths), and a
15+
Horwitz–Reps–Binkley **SDG** (formal/actual parameter nodes, CALL/PARAM_IN/PARAM_OUT edges,
16+
SUMMARY edges from bottom-up relational function summaries over the Tarjan SCC condensation;
17+
globals as extra formals, closure captures bound at definition sites). Emitted as the
18+
`program_graphs` section of `analysis.json` (own `schema_version` 1.0.0), keyed by the same
19+
callable signatures as the symbol table and call graph.
20+
- **Context-sensitive backward slicing** as an SDG query (`codeanalyzer.dataflow.slicing`, HRB
21+
two-phase traversal). Taint is deliberately left to the CLDK SDK — post-SDG it is
22+
language-independent labeled reachability.
23+
- **CPG overlay in the Neo4j projection** at level 3: `CFGNode` nodes plus the shared
24+
cross-language `HAS_CFG_NODE`/`CFG_NEXT`/`CDG`/`DDG`/`PARAM_IN`/`PARAM_OUT`/`SUMMARY` edge
25+
vocabulary. `schema.neo4j.json` bumped additively to **1.2.0**.
26+
- **New flags**: `--graphs cfg,dfg,pdg,sdg` (scopes the emitted sections; strict validation —
27+
unknown values or use below `-a 3` exit non-zero) and `--graph-field-depth` (access-path
28+
k-limit, default 3 — the bound that guarantees the interprocedural fixpoint terminates).
29+
- **Alias oracle (MVP)**: type-based may-alias using Jedi-inferred types (unknown types
30+
conservatively alias); frozen behind `may_alias()` for a later points-to upgrade.
31+
32+
### Changed
33+
- `-a/--analysis-level` now accepts `3`; levels stay cumulative (level 3 includes PyCG
34+
enrichment). `-a 1`/`-a 2` output and timings are unchanged.
35+
836
## [0.3.0] - 2026-06-27
937

1038
### Added

0 commit comments

Comments
 (0)