fix(extensions): one AST depth bound, so the two walks over author source cannot diverge - #3082
fix(extensions): one AST depth bound, so the two walks over author source cannot diverge#3082BIMvoice wants to merge 1 commit into
Conversation
#3025 gave `checkBannedConstructs` (`host/source-wrap.ts`) a private `MAX_AST_DEPTH = 1000`; #3027, the same day, gave `ast/bounded-walk.ts` its own `1000`. Both bound traversals over extension-author-supplied source and both REFUSE a script past the bound, so raising or lowering one alone would open a band where `wrapEntrySource` accepts a script `validateCode` refuses, silently. `source-wrap.ts` now imports the constant. The value is unchanged, so no script changes verdict. Demonstrated rather than asserted: with the shared constant dropped to 100, a 300-level script is refused by both `wrapEntrySource` and `validateCode`; restoring `source-wrap.ts`'s private `1000` under the same mutation reproduces the band exactly — `wrapEntrySource` accepts what `validateCode` refuses. `bounded-walk.ts`'s module doc claimed to be "the single traversal used by every AST consumer here" and that callers "do not re-implement the traversal" — never true of `checkBannedConstructs`, which enumerates child properties generically instead of descending through `acorn-walk`'s `base`. The doc now names which walks go through the module (`validate/code.ts`, `inference/capability.ts`) and which does not, and does not claim the two cut the same script at the same node: they count a level differently and share only the budget. Verification: `@ifc-lite/extensions` vitest 810 passed / 0 failed across 60 files both before and after, `tsc --noEmit` and `scripts/typecheck-tests.mjs` (60 test files) clean, oxlint clean on both changed files, `check-changesets` exit 0.
|
Warning Review limit reached
Next review available in: 8 minutes Limit details: You’ve used all 2 included reviews currently available. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?Wait for the limit to reset, then comment An organization admin can change what happens after included review limits in Billing. How do review limits work?CodeRabbit enforces per-developer PR review limits within each organization. For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Viewer benchmark✅ No threshold regressions detected. 01_Snowdon_Towers_Sample_Structural(1).ifcBaseline recorded 2026-07-01T20:31:05.538Z on github-actions ubuntu-latest, viewer-benchmark-ci (headless Chrome, SwiftShader ANGLE), production build.
AC20-FZK-Haus.ifcBaseline recorded 2026-07-01T20:30:59.972Z on github-actions ubuntu-latest, viewer-benchmark-ci (headless Chrome, SwiftShader ANGLE), production build.
Refresh the baseline from a CI run: dispatch the Benchmark workflow with |
|
Closing as superseded by #3070, which is now merged. Proved the subsumption rather than inferring it from the titles, because both PRs are about "one AST depth bound" and it would be easy to assume they are the same change. They are not. #3070 removes the duplicate traversal; this one keeps it and shares only the constant. Measured against both branch heads, in This PR does: import { MAX_AST_DEPTH as SHARED_MAX_AST_DEPTH } from '../ast/bounded-walk.js';
const MAX_AST_DEPTH = SHARED_MAX_AST_DEPTH;
...
if (depth > MAX_AST_DEPTH) { // still its own traversal#3070 does: import { MAX_AST_DEPTH, walkBounded } from '../ast/bounded-walk.js';and deletes the second walk entirely. Confirmed on So the divergence band this PR documents is closed more thoroughly by #3070: sharing a constant between two hand-written walks leaves two walks that can still drift in what they visit, which is the wider version of the same fault. The table in this PR's body — showing Nothing lost. If anything here is not covered by #3070, reopen and say which and I will look again. Credit where it is due: the depth-parity fixture on #3070 is better than what I suggested when I raised the vacuous-straddle problem. I said the arrow range never crossed the bound. The commit found the sharper thing — that reusing |
Two copies of
MAX_AST_DEPTHbound walks over extension-author source, and moving one alone opens a band where one accepts what the other refuses.packages/extensions/src/host/source-wrap.ts:156held a private1000(from #3025);packages/extensions/src/ast/bounded-walk.ts:55held another (from #3027, merged the same day). Both refuse a script past the bound.The divergence band, reproduced directly
A throwaway probe exercising
wrapEntrySourceandvalidateCodeon the same 300-level nested script:source-wrap.tswrapEntrySourcevalidateCode1000(pre-fix)The third row is the defect: before this change, moving the bound makes
wrapEntrySourceaccept exactly whatvalidateCoderefuses. After it, that row is unreachable.The value is unchanged, so no script changes verdict.
source-wrap.test.ts:163still passes asserting the literal'Entry script is nested more than 1000 AST levels deep.'A doc that overclaimed
bounded-walk.tsdescribed itself as "the single traversal used by every AST consumer here", with callers who "do not re-implement the traversal".checkBannedConstructsalways did.The corrected doc deliberately does not claim the two cut the same script at the same node — they count a level differently, and the mutation table above is what that distinction looks like. They share the budget, nothing more.
One claim in it is established by reading rather than running: that
checkBannedConstructs's generic property enumeration visits a superset of the positionsacorn-walk'sbasedescends. It is sound from the code — generic enumeration reaches computed member properties, object keys and labels thatbaseskips — but "superset" is a stronger word than the evidence, so read it as "a different, broader set".Checks
packages/extensions60 files / 810 tests passed, 0 failed atupstream/main, and 60 / 810 passed, 0 failed with the patch — identical, which is the expected shape of a no-behaviour-change consolidation.No cycle:
ast/bounded-walk.tsimports onlyacorn-walk, and the new edge ishost/ → ast/, one-way. No host-only code enteredast/; that file's change is doc-only.A third copy? Not in production —
MAX_AST_DEPTHnow has exactly one definition, consumed byvalidate/code.ts,inference/capability.tsandhost/source-wrap.ts, and a repo-wide grep forAST_DEPTH/nested more than/AST levels deepoutsidepackages/extensionsreturns nothing.The literal
1000does survive insource-wrap.test.ts:163, which asserts the full message string. Left deliberately: pinning the value in a test is a guard rather than a divergence hole, since changing the constant now fails loudly there instead of passing silently. Worth knowing that a future change to the bound touches that assertion —validate/code.test.tsuses/nested more than \d+/and would not notice.tsc --noEmitexit 0;typecheck-testsOK across 60 files; oxlint clean on both changed files;check-changesetsexit 0.🤖 Generated with Claude Code