@@ -69,3 +69,85 @@ additions, all additive:
6969- ` CALL ` SDG edges are not projected: the callable-level ` PY_CALLS ` twin
7070 already carries calls; callsite-statement granularity is recoverable via
7171 ` PY_HAS_CALLSITE ` /` PY_RESOLVES_TO ` .
72+
73+ ## Stage 0 — Scalpel oracle spike
74+
75+ Research spike (issue #70 ) verifying ** SMAT-Lab/Scalpel** as the primary L4
76+ may-alias oracle before the interprocedural-dataflow stage writes any
77+ integration. No product code changed; a throwaway probe under ` test/spikes/ `
78+ was run and deleted.
79+
80+ ** Decision.** Primary oracle = ** ` ScalpelAliasOracle ` ** implementing the
81+ frozen interface ` may_alias(path_a: str, path_b: str) -> bool ` ; automatic
82+ fallback = the existing ` TypeBasedAliasOracle ` (for constructs Scalpel can't
83+ resolve and for parse/build failures, keeping the interface total).
84+
85+ ** Verdict: Scalpel is VIABLE** as the L4 oracle — consumed as ** SSA + copy/const
86+ facts** , not as a turnkey points-to engine (Scalpel ships no Andersen/
87+ Steensgaard heap analysis; its "alias pairs" are copy/const records over SSA).
88+
89+ ** Environment.** Installed ` python-scalpel==1.0b0 ` (self-reports
90+ ` __version__ == "1.0dev" ` ) via ` uv pip install python-scalpel ` into the repo's
91+ uv-managed ` .venv ` , ** CPython 3.12.13** (the project interpreter per
92+ ` pyvenv.cfg ` /` .envrc ` ; the bare ` python ` on PATH is a pyenv shim to 3.14.0 and
93+ is * not* the project env). ` import scalpel ` and ` import codeanalyzer ` both work
94+ afterward. ` uv pip install ` does not touch ` uv.lock ` ; installed packages live
95+ in the gitignored ` .venv ` .
96+
97+ ** Modules / classes / functions to consume, and their output shape:**
98+
99+ 1 . ** CFG substrate** — ` from scalpel.cfg import CFGBuilder ` ;
100+ ` CFGBuilder().build_from_src(name, src) ` (or ` build_from_file ` ). CFG is
101+ ** basic-block level** : nested-function CFGs in ` cfg.functioncfgs ` keyed by
102+ ` (entry_id, func_name) ` , params in ` cfg.function_args ` . ` Block.statements `
103+ are ** real ` ast ` statement nodes retaining ` .lineno ` /` .col_offset ` ** .
104+ 2 . ** SSA + alias** (there is * no* standalone alias module) — `from
105+ scalpel.SSA.const import SSA` ; ` ssa_results, const_dict =
106+ SSA().compute_SSA(func_cfg)`. ** Statement-level** on top of the block CFG.
107+ - ` ssa_results ` : ` dict[block_id] -> [ {var_name: {def_version_ints}} ] ` (one
108+ dict per statement) — the use-def chain; a version set with >1 element is
109+ a ** phi/merge** ; an empty set is a param/global/external use. Attribute
110+ access-path names (` a.field ` ) appear as keys.
111+ - ` const_dict ` : ` dict[(var_name, version)] -> ast value node ` — the ** alias
112+ carrier** . Value an ` ast.Name ` ⇒ a copy edge (` ('b',0)->Name 'a' ` means
113+ b aliases a); value an ` ast.Attribute ` ⇒ attribute-path store; ` <ret> `
114+ is the return-value pseudo-name. Version counter is function-global, so
115+ ` (name, version) ` is a stable intra-function SSA identity.
116+ 3 . ** Type inference** (for the type-guided branch) — `from
117+ scalpel.typeinfer.typeinfer import TypeInference`; ** file-based** :
118+ ` TypeInference(name, entry_point=path).infer_types(); get_types() ` →
119+ ` list[dict] ` rows `{file, line_number, function, type: set[ str] ,
120+ variable|parameter}` ; ` 'any'` = unknown.
121+
122+ ** Mapping onto access-path strings / ` (signature, node_id) ` .** Both the repo
123+ (` codeanalyzer/dataflow/cfg.py ` ` CFGNode ` carries ` start_line ` /` end_line ` +
124+ ` ast_node ` ) and Scalpel build from the ** same source AST** , so the join is by
125+ source position: function ⇄ ` functioncfgs ` key; node ⇄ statement AST
126+ ` (lineno, col_offset) ` equal to ` CFGNode.start_line ` (/col) ⇒ same ` node_id `
127+ (the integration can even reuse the repo AST, making it identity not a match).
128+ Scalpel var names use the same ` base(.field|[*])* ` grammar (normalize
129+ subscripts to ` [*] ` , re-` k_limit ` ). ` may_alias ` = transitively close
130+ ` const_dict ` ` Name ` →` Name ` /attribute copies into per-function equivalence
131+ classes; TRUE iff bases share a class ** and** suffixes are prefix-compatible
132+ (reuse ` suffix_of ` /prefix logic); for unrelated bases consult ` TypeInference `
133+ (incompatible concrete types ⇒ not aliased, ` any ` /unknown ⇒ may-alias); on
134+ anything unresolved, fall back to ` TypeBasedAliasOracle ` .
135+
136+ ** Probe answers.** (a) alias/SSA output ** is** keyable to `(function,
137+ line/col)` — verified end-to-end. (b) ** CFG block-level, SSA/use-def
138+ statement-level.** (c) modern syntax: ` walrus := ` OK, ` async ` /` await ` /`async
139+ with` OK, ` match` / ` case` ** does not crash** but is ** not** split into per-arm
140+ CFG branches (block-level imprecision, mitigated by the repo's own
141+ statement-level CFG).
142+
143+ ** Concerns carried to Stage 4.** Copy-only + intra-function (no heap
144+ points-to; two params/aliased container elements are not modeled — the
145+ type-guided branch + sound-leaning fallback must cover them); ` match ` arms
146+ unbranched in Scalpel's CFG; dependency hygiene — ` python-scalpel ` is a
147+ low-maintenance pre-release that drags in ` typed-ast ` (C build, historically
148+ fails on 3.13+) and a ` dataclasses ` backport (harmlessly shadowed on 3.12), so
149+ pin/constrain them, gate the import as a soft dependency (missing/broken
150+ Scalpel → fallback, not a hard failure), and confirm the build across the repo's
151+ supported 3.9–3.13+ range; ` compute_SSA ` 's return contract is discovered from
152+ source (not a documented public API) — wrap it behind ` ScalpelAliasOracle ` to
153+ contain upstream drift.
0 commit comments