Skip to content

Commit fd06b8f

Browse files
committed
Reject higher-order recursion trampolines
1 parent 40b3a33 commit fd06b8f

16 files changed

Lines changed: 504 additions & 42 deletions

CHANGELOG.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88
## [Unreleased]
99

1010
### Changed
11+
- Semantic recursion validation now catches higher-order callback trampolines,
12+
including direct, mutual, and callback-parameter-alias cycles.
1113
- Normal example verifier runs now fail closed when a golden output fixture is
1214
missing; only explicit `--update-golden` runs write fixture files.
1315
- Documentation now scopes the low-recursion implementation claim to the
@@ -98,9 +100,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
98100
wheel in a clean virtual environment before upload.
99101
- Native example release archives are explicitly named for their current
100102
`linux-x86_64` / Zig 0.15.2 artifact contract.
101-
- Semantic validation now rejects direct, mutual, and local function-pointer
102-
alias recursion; source programs should use loops, explicit stacks, or
103-
index-based worklists instead.
103+
- Semantic validation now rejects direct, mutual, local function-pointer alias,
104+
and higher-order callback trampoline recursion; source programs should use
105+
loops, explicit stacks, or index-based worklists instead.
104106
- Index and slice-bound variables now require `usize`; non-negative integer
105107
literals remain valid for simple indexing, while signed index variables and
106108
negative literals are rejected.

COMPLETION_AUDIT.md

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -22,26 +22,27 @@ Deliverables implied by the active objective:
2222
| Requirement | Evidence | Status |
2323
| --- | --- | --- |
2424
| Vulnerability/problem review | `RELEASE_READINESS_REVIEW.md`, `SECURITY.md`, `MISSING_FEATURES.md`, `TODO.md` | Covered for current known risks |
25-
| Security dependency audit | Hosted CI run `25549416603`; manual release workflow `25546125531`; local `uvx --from pip-audit==2.10.0 pip-audit --strict`; local site runtime audit | Passing for known advisories; CI/release tool version is pinned |
26-
| Python static security scan | Hosted CI run `25549416603`; local `uvx --from bandit==1.9.4 bandit -r a7 scripts main.py -q --skip B404,B603`; CI/release workflow step | Passing after resolving the release-manifest partial `git` path and marking the diagnostic-code false positive; CI/release tool version is pinned |
27-
| Secret scanning | Hosted CI run `25549416603`; `scripts/check_no_secrets.py` | Passing pattern- and filename-based scan |
28-
| Python test suite | Hosted CI run `25549416603`; local `./run_all_tests.sh` after selected-import JSON diagnostics hardening | Passing: 1250 tests |
29-
| Error-stage behavior | Hosted CI run `25549416603`; `scripts/verify_error_stages.py`; refactored shared logic in `scripts/error_stage_common.py` | Passing |
30-
| Zig example E2E | Hosted CI run `25549416603`; local `scripts/verify_examples_e2e.py`; shared verifier logic in `scripts/verify_examples_common.py`; manual golden-output inspection | Passing: 38/38 |
31-
| C example E2E | Hosted CI run `25549416603`; local `scripts/verify_examples_e2e_c.py`; shared verifier logic in `scripts/verify_examples_common.py`; manual golden-output inspection | Passing: 38/38 |
32-
| Zig/C backend parity | Hosted CI run `25549416603`; local expanded `scripts/verify_backend_parity.py`; manual report inspection; local full gate | Passing selected suite: 24/24 locally, including fallthrough, nested fallthrough, capture patterns, generic functions, type-set constraints, generic struct instances, explicit enum discriminants, stdlib math, and operator edge cases |
33-
| Debug artifacts | Hosted CI run `25549416603`; local `./run_all_tests.sh` after selected-import JSON diagnostics hardening | Passing: 76/76 |
34-
| Release artifacts | Hosted CI run `25549416603`; manual release workflow `25546125531`; local `./run_all_tests.sh` after selected-import JSON diagnostics hardening | Passing: 76/76 |
35-
| Python package build and install | Hosted CI run `25549416603`; manual release workflow `25546125531`; local clean `rm -rf dist && uv build`; `scripts/verify_wheel_install.py`; focused release tooling tests | Passing; built wheel installs as package `a7` and exposes `a7` CLI |
25+
| Security dependency audit | Hosted CI run `25551465003`; manual release workflow `25546125531`; local `uvx --from pip-audit==2.10.0 pip-audit --strict`; local site runtime audit | Passing for known advisories; CI/release tool version is pinned |
26+
| Python static security scan | Hosted CI run `25551465003`; local `uvx --from bandit==1.9.4 bandit -r a7 scripts main.py -q --skip B404,B603`; CI/release workflow step | Passing after resolving the release-manifest partial `git` path and marking the diagnostic-code false positive; CI/release tool version is pinned |
27+
| Secret scanning | Hosted CI run `25551465003`; `scripts/check_no_secrets.py` | Passing pattern- and filename-based scan |
28+
| Python test suite | Hosted CI run `25551465003`; local `./run_all_tests.sh` after higher-order recursion hardening | Passing: 1257 tests |
29+
| Error-stage behavior | Hosted CI run `25551465003`; `scripts/verify_error_stages.py`; refactored shared logic in `scripts/error_stage_common.py` | Passing |
30+
| Zig example E2E | Hosted CI run `25551465003`; local `scripts/verify_examples_e2e.py`; shared verifier logic in `scripts/verify_examples_common.py`; manual golden-output inspection | Passing: 38/38 |
31+
| C example E2E | Hosted CI run `25551465003`; local `scripts/verify_examples_e2e_c.py`; shared verifier logic in `scripts/verify_examples_common.py`; manual golden-output inspection | Passing: 38/38 |
32+
| Example golden fixture integrity | `scripts/verify_examples_common.py`; `test/test_examples_e2e.py::test_examples_verifier_fails_when_golden_fixture_is_missing`; local `uv run pytest test/test_examples_e2e.py -q`; hosted CI run `25551465003` | Normal verifier runs fail closed when a golden output fixture is missing; only explicit `--update-golden` writes fixture files |
33+
| Zig/C backend parity | Hosted CI run `25551465003`; local expanded `scripts/verify_backend_parity.py`; manual report inspection; local full gate | Passing selected suite: 24/24 locally, including fallthrough, nested fallthrough, capture patterns, generic functions, type-set constraints, generic struct instances, explicit enum discriminants, stdlib math, and operator edge cases |
34+
| Debug artifacts | Hosted CI run `25551465003`; local `./run_all_tests.sh` after fail-closed example-golden hardening | Passing: 76/76 |
35+
| Release artifacts | Hosted CI run `25551465003`; manual release workflow `25546125531`; local `./run_all_tests.sh` after fail-closed example-golden hardening | Passing: 76/76 |
36+
| Python package build and install | Hosted CI run `25551465003`; manual release workflow `25546125531`; local clean `rm -rf dist && uv build`; `scripts/verify_wheel_install.py`; focused release tooling tests | Passing; built wheel installs as package `a7` and exposes `a7` CLI |
3637
| Local package hygiene | `README.md`, `RELEASE.md`, `site/public/docs/release.md` now require `rm -rf dist` before `uv build` | Covered |
3738
| Release checksums, provenance, and archive contents | `scripts/generate_release_manifest.py`; `scripts/verify_release_manifest.py`; `scripts/verify_archive_contents.py`; `test/test_release_tooling.py`; release workflow validates required paths, required archive members, re-checks hashes before upload, and emits GitHub artifact attestations for release assets; manual release dispatch `25546125531` | Covered for workflow-dispatch release path; manifest verifier now rejects traversal and unsafe absolute paths; tag-only draft release creation still requires a real tag run before release |
38-
| Docs style/build | Hosted CI run `25549416603`; local `scripts/check_docs_style.py`; local `cd site && npm run build` | Passing |
39-
| Docs deploy | Hosted Deploy Docs run `25549416591`; hosted browser-harness check for `/a7-py/` confirmed the A7-first homepage title and primary navigation; hosted fetch confirmed the new `llms-full.txt` format | Passing |
39+
| Docs style/build | Hosted CI run `25551465003`; local `scripts/check_docs_style.py`; local `cd site && npm run build` | Passing |
40+
| Docs deploy | Hosted Deploy Docs run `25551465037`; hosted browser-harness check for `/a7-py/` confirmed the A7-first homepage title and primary navigation; hosted fetch confirmed the new `llms-full.txt` format | Passing |
4041
| curl.md/agent documentation | `site/public/llms.txt`, `site/public/llms-full.txt`, `site/public/docs/*.md`, plugin/dev subtrees, sitemap and robots entries | Implemented |
4142
| Release workflow | `.github/workflows/release.yml`, manual dispatch run `25546125531` on commit `8c24063`; release gate, dependency audits, static scan, package build, wheel install, docs build, archive verification, checksums, attestations, and artifact upload passed | Passing for non-tag validation; tag-only draft release creation still requires a real tag run before release |
4243
| Workflow supply-chain hardening | All workflow actions are pinned to immutable commit SHAs; `.github/dependabot.yml` covers GitHub Actions, Python, and docs npm; automated Claude review prompt treats PR text as untrusted | Covered for current workflow action pinning |
43-
| No-recursion language rule | Semantic recursion rejection, docs in `README.md`, `docs/SPEC.md`, and site docs; alias-recursion regression tests | Implemented for named call cycles and local function-pointer alias cycles |
44-
| No-recursion compiler traversal confidence | Iterative traversal tests and full gate | Covered for tested traversal paths |
44+
| No-recursion language rule | Semantic recursion rejection, docs in `README.md`, `docs/SPEC.md`, and site docs; direct, mutual, alias-recursion, callback trampoline, and forwarded callback trampoline regression tests; local full gate | Implemented for named call cycles, local function-pointer alias cycles, top-level functions passed through callback parameters that are invoked by the callee, and one-level callback-parameter forwarding through another callback |
45+
| No-recursion compiler traversal confidence | `test/test_iterative_traversal.py`; `README.md`; `TODO.md`; local full gate | Partially covered: semantic analysis, preprocessing, generic lowering, JSON AST output, and formatter/reporting AST walks are stack-based or tested at low recursion limits; parser is recursive descent, and backend statement/expression emission still has recursive paths tracked in `TODO.md` |
4546
| Virtual stdlib module resolution | `a7/module_resolver.py`, `a7/stdlib/__init__.py`, `test/test_module_resolver.py`, focused alias codegen tests | Implemented for `std/io`, `io`, `std/math`, and `math` |
4647
| File-backed module path containment | `a7/module_resolver.py`, `test/test_module_resolver.py` | Absolute imports and parent-directory traversal are rejected outside configured search paths |
4748
| Array literal assignment compatibility | `a7/passes/type_checker.py`, `a7/backends/c.py`, `test/test_semantic_types.py`, `test/test_codegen_c.py`, `scripts/verify_backend_parity.py`; local full gate | Covered for declared lengths, nested literals, and Zig/C runtime parity |
@@ -56,18 +57,21 @@ These prevent a factual "100% confident" claim:
5657
1. The compiler is not a sandbox; native output can execute host-level behavior.
5758
2. Backend parity is selected and expanding, not exhaustive over all valid A7
5859
programs. The current local selected suite covers 24 non-example programs.
59-
3. `fall` and branch-local match capture patterns now lower in both native
60+
3. Backend code generation still contains recursive statement/expression
61+
emission paths; current low-recursion coverage covers representative
62+
programs but not a full backend conversion.
63+
4. `fall` and branch-local match capture patterns now lower in both native
6064
backends, but backend parity is still selected rather than exhaustive.
61-
4. Full ownership, borrowing, lifetime, use-after-free, and double-free
65+
5. Full ownership, borrowing, lifetime, use-after-free, and double-free
6266
guarantees are not implemented.
63-
5. Full generic specialization is incomplete beyond simple top-level generic
67+
6. Full generic specialization is incomplete beyond simple top-level generic
6468
functions and used generic struct instances lowered for the C backend.
65-
6. Tagged/discriminated union tag workflows remain incomplete. Untagged
69+
7. Tagged/discriminated union tag workflows remain incomplete. Untagged
6670
single-field union construction and field access now have focused semantic,
6771
C backend, Zig backend, CLI JSON, and example verifier coverage.
68-
7. Secret scanning is pattern-based and should be backed by repository-host
72+
8. Secret scanning is pattern-based and should be backed by repository-host
6973
protections for public release.
70-
8. Dependency audits cover known advisories, not unknown supply-chain
74+
9. Dependency audits cover known advisories, not unknown supply-chain
7175
compromise.
7276

7377
## Conclusion

MISSING_FEATURES.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
29. C backend existing-identifier match patterns lower as comparisons in statements and expressions.
4747
30. C backend raw `fn(...)` parameter and variable declarations lower as C function pointers.
4848
31. Semantic validation reports block-local unreachable statements after `ret`, valid `break`/`continue`, `fall`, and fully-terminating `if`/`match` statements.
49-
32. Semantic validation rejects direct, mutual, and local function-pointer alias recursion; repeated work must use loops, explicit stacks, or index-based worklists.
49+
32. Semantic validation rejects direct, mutual, local function-pointer alias, and higher-order callback trampoline recursion; repeated work must use loops, explicit stacks, or index-based worklists.
5050
33. Index and slice-bound variables must be `usize`; non-negative integer literals remain valid for simple indexing.
5151
34. `new [N]T` heap fixed arrays fail closed until the allocation model and both backends agree on representation.
5252
35. Ordering comparisons reject non-ordered types, and signed variables no longer implicitly assign to unsigned integer types.

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ Source (.a7) → Tokenizer → Parser → Semantic Analysis (3-pass) → AST Pre
147147
4. **AST Preprocessing**. Runs 9 sub-passes: sugar lowering, stdlib resolution, mutation and usage analysis, type inference, shadowing resolution, function hoisting, and constant folding.
148148
5. **Backend Code Generation**. Translates AST to valid Zig or C source code.
149149

150-
Semantic analysis, AST preprocessing, and formatter/reporting AST walks use explicit stacks. The parser is recursive descent, and backend code generation still uses visitor-style recursive emission in some paths. Current low-recursion coverage validates the supported pipeline at Python recursion limit 100 for representative programs. A7 source recursion, including common local function-pointer alias cycles, is rejected during semantic validation; use loops, explicit stacks, or index-based worklists instead.
150+
Semantic analysis, AST preprocessing, and formatter/reporting AST walks use explicit stacks. The parser is recursive descent, and backend code generation still uses visitor-style recursive emission in some paths. Current low-recursion coverage validates the supported pipeline at Python recursion limit 100 for representative programs. A7 source recursion, including local function-pointer aliases and higher-order callback trampolines, is rejected during semantic validation; use loops, explicit stacks, or index-based worklists instead.
151151

152152
## Integer Type Guidance
153153

@@ -162,7 +162,7 @@ Use fixed-width integers such as `i32`, `i64`, `u32`, or `u64` when the data its
162162
- **Types**: Primitives, arrays, slices, pointers, generics, raw and aliased function types, inline struct return values
163163
- **Declarations**: Functions, structs, enums, unions, variables, constants, type aliases
164164
- **Control Flow**: if/else, while, for loops, for-in, labeled loops with break/continue, match statements, defer
165-
- **Function Rules**: Direct and mutual recursion are semantic errors
165+
- **Function Rules**: Direct, mutual, alias-mediated, and callback-trampoline recursion are semantic errors
166166
- **Expressions**: All operators with proper precedence, casts, if-expressions, struct/array literals, untagged union field literals/access
167167
- **Memory**: Property-based pointer syntax (`.adr`, `.val`), scalar/struct `new` and `del`, defer cleanup. Heap fixed arrays (`new [N]T`) are rejected until the language model is defined.
168168
- **Imports**: Virtual `std/io` and `std/math` modules with aliases; file-backed local imports resolve for validation but fail closed before backend codegen until module linking is implemented

RELEASE_READINESS_REVIEW.md

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ The repository is substantially more release-ready than before this pass:
1818
upload/download, and draft GitHub releases
1919
- checksum-verified Zig 0.15.2 install steps in CI and release workflows
2020
- semantic recursion rejection for direct, mutual, and local function-pointer
21-
alias call cycles, with scope-aware handling for local function-pointer
22-
shadowing
21+
alias call cycles and higher-order callback trampolines, with scope-aware
22+
handling for local function-pointer shadowing
2323
- consolidated docs-site navigation with curl.md-friendly Markdown entry points
2424
under `site/public/llms.txt` and `site/public/docs/`
2525
- C backend specialization for simple top-level generic function calls and used
@@ -320,9 +320,12 @@ not factually provable from local tests alone.
320320
- README now points to the current `code5717.github.io/a7-py` documentation URL.
321321
- Formatter symbol collection no longer hides broad exceptions during console
322322
or Markdown report generation.
323-
- Semantic validation now rejects direct and mutual recursion and avoids false
324-
recursion reports when a local function-pointer variable shadows a top-level
325-
function name.
323+
- Semantic validation now rejects direct, mutual, and indirect callback
324+
recursion while avoiding false reports when a local function-pointer variable
325+
shadows a top-level function name.
326+
- Semantic validation now treats top-level functions passed into invoked
327+
callback parameters as call-graph edges, so higher-order trampolines cannot
328+
hide direct or mutual recursion.
326329
- Selected non-example programs now run through both Zig and C backends and
327330
compare runtime output, including match statements/expressions, slices,
328331
string slices, labels, function pointers, defer unwinding, untagged unions,
@@ -390,6 +393,18 @@ not factually provable from local tests alone.
390393
- `fall` now lowers in both native backends for the documented narrow form:
391394
the final direct statement of a non-final match case. Invalid placements
392395
remain semantic errors.
396+
- Example verification now fails closed when a golden output fixture is missing
397+
unless the verifier is run with explicit `--update-golden`.
398+
- Hosted CI run `25551465003` passed on commit `40b3a33`, including pytest,
399+
Python dependency audit, Bandit static security scanning, error-stage
400+
verification, Zig/C example verification, backend parity, debug artifacts,
401+
release artifacts, package build, and wheel-install verification.
402+
- Hosted Deploy Docs run `25551465037` passed on commit `40b3a33`.
403+
- Local `./run_all_tests.sh` passed after higher-order recursion hardening:
404+
parser/tokenizer 501 passed; semantic 340 passed; compiler/CLI/backend 335
405+
passed; Zig examples 38/38; C examples 38/38; backend parity 24/24; debug
406+
artifacts 76/76; release artifacts 76/76; error-stage checks 61/61; docs
407+
style ok; secrets check ok; total pytest 1257 passed; summary 12/12.
393408

394409
## Residual Risks
395410

TODO.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ Features that are spec'd and partially implemented, or missing from one backend.
225225
- [x] Flag dead code after unconditional return/break/continue.
226226
Notes: semantic validation now rejects block-local statements after `ret`, valid `break`/`continue`, `fall`, and fully-terminating `if`/`match` statements.
227227

228-
- [x] Reject direct, mutual, and local function-pointer alias recursion during semantic validation.
228+
- [x] Reject direct, mutual, local function-pointer alias, and higher-order callback trampoline recursion during semantic validation.
229229
Notes: A7 source must use loops, explicit stacks, or index-based worklists for repeated work; local aliases like `again := current_fn` are treated conservatively.
230230

231231
- [x] Require `usize` for index and slice-bound variables.

0 commit comments

Comments
 (0)