Repository navigation
fix(transform): don't deforest producers called inside closures (#5136) - #5179
Conversation
The interprocedural deforestation pass rewrites a "producer" function (`const out = []; …push…; return out;`) to take a trailing accumulator parameter and rewrites its call sites to pass one. Neither the producer-detection scans nor the phase-3 call-site rewriter descended into closure bodies — the shared `walk_expr_children` helper visits an `Expr::Closure`'s parameter defaults but never its statement body. So when a producer's only caller lived inside a closure (e.g. one returned from a factory), the producer's signature gained the extra param while the in-closure call site kept the original arity. That arity mismatch lowered to a garbage accumulator pointer and a SIGSEGV. Surfaced in the wild as `uuid`'s `v5()` crashing (exit 139) under `perry.compilePackages`: its `stringToBytes` producer is called from the `generateUUID` closure that `v35()` returns. Fix: add a detection pass that bails on any producer referenced inside a closure body, so both inside- and outside-closure call sites keep the original signature. This is conservative — it only ever shrinks the candidate set (forgoes an optimization), never miscompiles. Teaching the rewriter to descend into and re-scope closure bodies is left as a follow-up, consistent with the other deferred closure-walking refinements noted in detect.rs. Adds unit tests (in-closure caller bails; non-closure caller still deforests) and an end-to-end regression in test-files/. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughAdds a new scan pass that walks module statement trees to find producer ChangesClosure-safe deforestation guard
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@crates/perry-transform/src/deforest/scan.rs`:
- Around line 186-257: The wildcard pattern `_ => {}` in the match statement for
`scan_stmt_for_closures` (ending around line 256) silently skips five statement
variants that should be explicitly handled. Replace the catch-all pattern with
explicit match arms for the `Break`, `Continue`, `LabeledBreak`,
`LabeledContinue`, and `PreallocateBoxes` statement variants, each with `{}`
bodies, or replace the catch-all with a single comment explaining why these
variants intentionally require no processing. Apply the same fix to the
`StmtRefAllWalker::visit_stmt` method at the corresponding location (line 419),
ensuring both functions consistently handle all statement variants explicitly
rather than silently skipping them.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 01b77818-a55d-432a-a15a-2e1044dc28e2
📒 Files selected for processing (5)
crates/perry-transform/src/deforest/detect.rscrates/perry-transform/src/deforest/mod.rscrates/perry-transform/src/deforest/scan.rscrates/perry-transform/src/deforest/tests.rstest-files/test_deforest_closure_caller.ts
…eRabbit)
Replace the `_ => {}` catch-alls in `scan_stmt_for_closures` and
`StmtRefAllWalker::visit_stmt` with explicit leaf arms (Break, Continue,
LabeledBreak, LabeledContinue, PreallocateBoxes). These variants carry
no nested expressions or statements so behavior is unchanged, but
dropping the wildcard means a future `Stmt` variant that does carry a
closure forces a compile error here instead of being silently skipped.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Summary
Fixes #5136 —
uuid'sv5()segfaults (SIGSEGV / exit 139) underperry.compilePackages.Root cause
The interprocedural deforestation pass (
crates/perry-transform/src/deforest) rewrites a "producer" function —const out = []; …push…; return out;— to take a trailing__deforest_outaccumulator parameter, and rewrites its call sites to pass one.Neither the producer-detection scans nor the phase-3 call-site rewriter descend into closure bodies: the shared
walk_expr_childrenhelper visits anExpr::Closure's parameter defaults but never its statement body. So when a producer's only caller lives inside a closure (e.g. one returned from a factory), the producer's signature gains the extra param while the in-closure call site keeps the original arity:That arity mismatch makes
__deforest_outa garbage register, the function pushes into a junk pointer, and.lengthon the bogus result dereferences null → SIGSEGV.In
uuidthis is exactlyv5:v35()returns agenerateUUIDclosure that calls the module-levelstringToBytesproducer.The bug is general (not compile-package specific) — it reproduces in a plain
.tsfile:Fix
Add a fourth detection pass that bails on any producer referenced inside a closure body, so both inside- and outside-closure call sites keep the original signature. This is conservative — it only ever shrinks the candidate set (forgoes the optimization for that shape), and can never miscompile. Teaching the rewriter to descend into and correctly re-scope closure bodies is left as a follow-up, consistent with the other deferred closure-walking refinements already noted in
detect.rs.Verification
uuid9.0.1 source, compiled viacompilePackages) now matches Node byte-for-byte:6cb3836f-339d-52d8-acc6-8751229b61cf true 5.rejects_producer_called_inside_closure(bails) andstill_deforests_when_caller_is_not_a_closure(control — optimization still fires).test-files/test_deforest_closure_caller.ts.cargo test -p perry-transformgreen (32 + 8 deforest tests).🤖 Generated with Claude Code
Summary by CodeRabbit
#5136) where producer functions referenced inside closure bodies could be incorrectly selected for rewriting, leading to compilation failures or incorrect transformations. Producers used within closures are now safely excluded from transformation candidates.#5136.