Files
Open_Ultra_SAST/.kiro/specs/tree-sitter-overlay-extra/research.md
T
Marius Ciepluch e9f185da59 feat(tree-sitter-overlay-extra): parse JS/C/Java overlay via optional extra
Add openultrasast[semantic] with official grammar wheels and one CST walker
into FileIR. quick stays extra-free. Host CLI is not overlay-ready. Default CI
stays extra-free; a semantic job runs pytest -m semantic.
2026-09-04 15:07:35 +02:00

11 KiB
Raw Blame History

Gap Analysis: tree-sitter-overlay-extra

Requirements are generated but not approved. Analysis proceeds to inform design; it does not treat requirements as locked.

Steering product.md / tech.md / structure.md are absent. Context is the overlay package, harness_ext extra pattern, pyproject.toml, CI, and PyPI tree-sitter bindings.


1. Current State

Assets that already exist

Asset Role today
src/openultrasast/semantic/ir.py FileIR / Bind / CallSite; Python ast extractor (~170 lines); parse_tree_sitter_cli always returns None
src/openultrasast/semantic/engines.py Probe: env flag, importlib.util.find_spec("tree_sitter"), or tree-sitter CLI on PATH
src/openultrasast/semantic/taint.py Language-agnostic taint on FileIR (names, constants, fact tables)
src/openultrasast/semantic/overlay.py Dispositions; already consumes parse_file
src/openultrasast/ruleset/semantic/*.toml JS/C/Java facts already written
src/openultrasast/harness_ext.py Pattern for optional extra: find_spec, lazy import, mypy override, cold-import tests
pyproject.toml dependencies = []; only extra is harnessx
tests/test_semantic_overlay.py JS unsupported without probe; JS IR only via monkeypatch, not a real grammar

Conventions

  • Optional native/heavy deps stay off the default install; capability probe must not import at module load (harness_ext, Docker sandbox probe).
  • mypy ignore_missing_imports for extras (harnessx.*).
  • pytest marker already exists for docker; extra-enabled tests would follow that skip pattern.
  • CI checks job: uv sync --group dev then pytest — no optional extras.

Integration surfaces

  • parse_file(path, text, language) -> FileIR is the only seam taint/overlay need.
  • tree_sitter_available() is a presence probe, not “grammar produced IR”. Host CLI on PATH makes it True even with no PyPI wheels (this environment has /home/linuxbrew/.../tree-sitter and no languages).
  • Pair eval already falls back to inventory when a language never promotes/demotes.

2. Requirements Feasibility

Req Need Gap
1 Extra-free quick Empty core deps; no import of parser wheels at load Constraint met. Missing extra cannot currently break quick because there is no extra. Must keep it that way.
2 Structured parse for Py/JS/C/C++/Java Real CST → FileIR; JS promote; C/Java demote on constant/sanitizer Missing. CLI parse never builds FileIR. No [semantic] extra. No CST walker.
3 Fail closed language_unsupported / parse_failed; Python ast last resort Mostly present via parse_file fallback. Syntax-error Python already parse_failed. JS/C/Java without IR already language_unsupported.
4 One shared extract path Not a visitor per language; CLI without extra must not fake success Missing walker. CLI stub already returns None (does not fake success). Probe can still report “available” without IR — constraint for Req 4.3 wording vs tree_sitter_available().
5 Overlay contracts No LLM parse; Joern not required; corroboration; promotions-only sandbox; gate inventory-only Present in overlay/prove/gate. Do not reopen.
6 Dual test matrix Extra-free always; extra-on JS + C/Java; skip if extra absent Partial. Extra-free tests exist. Extra-on tests are mocked. No pytest marker. CI does not install a semantic extra.

Complexity signal: external native-wheel extra + one algorithmic CST walk + pytest/CI matrix. Not CRUD.

Research needed (design phase, not blockers):

  1. Pin compatible tree-sitter + tree-sitter-{python,javascript,c,cpp,java} wheel ABIs (Language(mod.language()) vs older build_library). PyPI: tree-sitter 0.26 (2026-06-30), tree-sitter-javascript 0.25.
  2. Per-language CST node names for assignment, call, identifier, string, function (JS assignment_expression vs C assignment_expression vs Java assignment_expression vs Python assignment). Shared walker needs a small node-kind table, not four visitors.
  3. C++: separate tree-sitter-cpp vs C grammar; preprocess maps .cpp to cpp.
  4. Whether default CI should add a second job uv sync --extra semantic or only skip-marked tests on the extra-free job.

3. Implementation Approach Options

Option A: Extend ir.py / engines.py only

Put extra pins in pyproject.toml, implement API parse + CST walk inside ir.py, tighten tree_sitter_available().

  • Pros: Few files; parse_file stays the seam.
  • Cons: ir.py is already ~277 lines (Python ast + dead CLI). A walker + language table will bloat it and mix packaging probe with extract.

Option B: New components only

New semantic/cst.py + semantic/extra.py; leave ir.py as Python ast; overlay calls a new entrypoint.

  • Pros: Clean extra guard.
  • Cons: Two parse entrypoints; easy to fork parse_file consumers.
  • New: semantic/cst.py — one walker: tree-sitter Node → Bind / CallSite / FunctionIR using a node-kind map.
  • New or thin: extra guard modeled on harness_ext (has_semantic_extra, lazy grammar load, never import wheels at module load).
  • Extend: pyproject.toml [project.optional-dependencies] semantic = [...]; mypy override tree_sitter*; pytest marker semantic; CI extra-free job unchanged; optional second job or skip.
  • Extend: parse_file prefers extra CST when a grammar loads; else Python ast; else language_unsupported. Delete or stop calling parse_tree_sitter_cli as a success path (Req 4.3).
  • Do not extend: taint.py, overlay.py, facts, gate, pair catalog.

Trade-offs: Extra files, but ir.py shrinks (drop dead CLI), taint stays language-agnostic, extra follows an existing packaging seam.

4. Effort and Risk

  • Effort: M (3–7 days). Extra pins + walker + dual tests. Overlay/taint reuse. Not a new adjudicator.
  • Risk: Medium. Native wheels and grammar ABI pins; CST node-name drift across languages; CI without extra must stay green. Mitigate with skip markers and a version pin table in design.

5. Design-phase recommendations

  • Prefer Option C.
  • Treat FileIR as frozen contract; do not add dispositions.
  • Decide probe semantics: tree_sitter_available should mean “can produce FileIR for this language”, not “CLI binary exists”.
  • Carry research items 1–4 into design (ABI pins, node-kind map, cpp package, CI job vs skip).
  • Do not pull Joern, pair harvest, or CWE-121 into this spec.

Summary (design discovery)

  • Feature: tree-sitter-overlay-extra
  • Discovery Scope: Extension of parse_file / overlay IR; optional extra packaging
  • Key Findings:
    • Official PyPI wheels: tree-sitter (0.26), tree-sitter-python, tree-sitter-javascript (0.25), tree-sitter-c (0.24.x), tree-sitter-cpp (0.23.4), tree-sitter-java. API is Parser(Language(mod.language())); build_library is gone.
    • FileIR is the only overlay seam; taint does not need language visitors.
    • Host CLI presence must not count as a successful overlay parse (Req 4.3).

Research Log

PyPI ABI and load API

Probe vs grammar

  • Context: tree_sitter_available() is true when CLI is on PATH with zero grammars.
  • Findings: CLI parse cannot populate FileIR without a walker. Extra Python API is the product parse.
  • Implications: Split has_semantic_extra() from per-language grammar_for. Remove CLI as a success path.

Architecture Pattern Evaluation

Option Description Strengths Risks Notes
A Extend ir.py only Walker + extra inside ir.py Few files Bloats 277-line module Rejected
B New entrypoint Overlay calls cst.py directly Clean extra Forks parse_file Rejected
C Hybrid Extra guard + cst walker; parse_file dispatch Matches harness_ext; FileIR frozen Node-kind table Selected

Design Decisions

Decision: Adopt official grammar wheels, build one walker

  • Context: Avoid per-language AST visitors (Req 4).
  • Alternatives: tree-sitter CLI sexp; py-tree-sitter-languages (unmaintained); hand-rolled JS/C parsers.
  • Selected: openultrasast[semantic] = tree-sitter plus python/javascript/c/cpp/java grammar packages. One CST walk keyed by a node-kind map.
  • Rationale: Wheels are MIT, maintained, match in-scope languages. Taint stays language-agnostic.
  • Trade-offs: Native wheels in the extra; ABI pins. Core install stays empty.
  • Follow-up: Verify Parser constructor against the pinned tree-sitter minor at implement time.

Decision: Extra-free CI stays default; extra-on is a second job plus skip marker

  • Context: Req 6 dual matrix.
  • Selected: pytest semantic marker skips when extra absent. GitHub Actions adds a job uv sync --extra semantic --group dev that runs marked tests. Default checks job unchanged.
  • Rationale: Default CI must not fail without wheels. Extra-on tests must actually run somewhere.
  • Trade-offs: Two pytest invocations in CI.

Decision: has_error implies parse_failed

  • Context: tree-sitter is error-tolerant; Req 3.4 wants fail-closed on syntax errors.
  • Selected: If tree.root_node.has_error (or ERROR children), return FileIR(parse_ok=False, reason=parse_failed).
  • Rationale: Partial IR would invite false demote.

Synthesis

  • Generalization: One parse_file dispatch; extra is a grammar source, not a second overlay.
  • Build vs adopt: Adopt tree-sitter wheels; build only the FileIR projection (binds/calls).
  • Simplification: No CLI sexp walker; no second parse API; no taint/overlay changes.

Risks & Mitigations

  • Grammar ABI mismatch — pin extras together; extra-on CI job catches import/parse failure.
  • Node-type drift — table in cst.py; tests assert JS eval and C printf on real trees.
  • CLI still on PATH — must not set parse_ok.

References