mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-18 20:49:28 +00:00
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.
172 lines
11 KiB
Markdown
172 lines
11 KiB
Markdown
# 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.
|
||
|
||
### Option C: Hybrid (recommended for design)
|
||
|
||
- **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
|
||
|
||
- **Context**: Pin wheels that work on Python 3.11+ without compiling grammars in CI.
|
||
- **Sources Consulted**: https://pypi.org/project/tree-sitter/ ; https://pypi.org/project/tree-sitter-javascript/ ; https://pypi.org/project/tree-sitter-cpp/ ; py-tree-sitter 0.23 release notes (Language capsule, Parser constructor).
|
||
- **Findings**: Grammars ship abi3 wheels. Load with `Language(tree_sitter_<lang>.language())` then `Parser(language)`. Do not use `Language.build_library`.
|
||
- **Implications**: Extra lists core + five grammar packages. mypy ignores `tree_sitter*`.
|
||
|
||
### 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
|
||
|
||
- https://pypi.org/project/tree-sitter/
|
||
- https://github.com/tree-sitter/py-tree-sitter/releases
|
||
- `src/openultrasast/harness_ext.py` extra guard pattern
|
||
|