bun:jsc: release throw scope before JSONParse in generateHeapSnapshotForDebugging - #36857
bun:jsc: release throw scope before JSONParse in generateHeapSnapshotForDebugging#36857robobun wants to merge 2 commits into
Conversation
…ForDebugging functionGenerateHeapSnapshotForDebugging declares a throw scope, asserts no exception after building the snapshot, then returns JSValue::encode(JSONParse(...)) without releasing the scope. JSONParse (LiteralParser::parseRecursively) can throw, so under BUN_JSC_validateExceptionChecks=1 the process aborts with 'exception check validation failed' naming this function. Wrap the return in RELEASE_AND_RETURN(scope, ...) to match the upstream jsc.cpp implementation of the same function.
WalkthroughHeap snapshot generation now returns parsed JSON through ChangesHeap snapshot generation
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
Comment |
|
Status: diff is green, ready for review. CI is failing on unrelated flakes. Reproduced with CI (builds #88371, #88421): the new
Needs a maintainer to merge. |
There was a problem hiding this comment.
LGTM — one-line RELEASE_AND_RETURN around a throwing tail call, matching the upstream jsc.cpp pattern.
What was reviewed:
- Confirmed
JSONParseis a throwing tail call under an activeDECLARE_THROW_SCOPE;RELEASE_AND_RETURNis the canonical fix per REVIEW.md. - Checked sibling
JSONParsesites in the same file —functionSamplingProfilerStackTracesreleases viareleaseAssertNoException()after the parse, andfunctionMemoryUsageStatisticshas no throw scope, so neither shares this bug. - Test spawns with
BUN_JSC_validateExceptionChecks=1, drains pipes concurrently, spreadsbunEnv, and asserts on exit code + stdout + a filtered stderr view (not a bare "no panic" check).
Extended reasoning...
Overview
One-line change in src/jsc/modules/BunJSCModule.h: the return statement in functionGenerateHeapSnapshotForDebugging is wrapped in RELEASE_AND_RETURN(scope, ...) so the throw scope is released before JSONParse (which can throw via LiteralParser::parseRecursively). A regression test is added to test/js/bun/jsc/bun-jsc.test.ts that spawns a subprocess with BUN_JSC_validateExceptionChecks=1 and asserts exit 0, ok object on stdout, and no unchecked-scope diagnostics on stderr.
Security risks
None. This is a debug-assertion hygiene fix in a diagnostics-only API (bun:jsc heap snapshot). No new inputs are parsed, no trust boundaries change, and release-build behavior is unchanged.
Level of scrutiny
Low. RELEASE_AND_RETURN on a throwing tail call is the exact idiom REVIEW.md prescribes ("Throwing tail calls go through RELEASE_AND_RETURN"), and the PR description cites the matching upstream jsc.cpp implementations of functionGenerateHeapSnapshot / functionGenerateHeapSnapshotForGCDebugging. The change is mechanical and self-evidently correct.
Other factors
- Sibling sites: I checked the other
JSONParsecallers in this file.functionSamplingProfilerStackTracescallsscope.releaseAssertNoException()immediately afterJSONParse(releasing the scope), andfunctionMemoryUsageStatistics/ thereportlambda infunctionRunProfilereither lack a throw scope or check the exception afterward — none share the exact unchecked-tail-return pattern fixed here. - Test quality: The test follows harness conventions —
await usingon the spawn,Promise.allon stdout/stderr/exited,{...bunEnv, ...}spread, and a combined-object assertion. It filters stderr for the specific validator diagnostic lines rather than doing a blanket "stderr is empty" check, which keeps it robust against unrelated debug logging. The comment honestly notes that on release builds the env var is a no-op and the test just exercises the snapshot path, so it will not vacuously fail on release CI lanes. - Verification: The robobun status comment reports the repro (exit 134 before, exit 0 after) and a clean 37-pass run of the full
bun-jsc.test.tssuite.
|
This PR may be a duplicate of:
🤖 Generated with Claude Code |
|
Not a duplicate of #34640. That PR is about making DFG This PR is the standalone fix for the exception-check finding with a dedicated validator test, no WebKit dependency. Whichever lands first, the other rebases cleanly: if this lands first, #34640 drops its one-line BunJSCModule.h hunk; if #34640 lands first, this becomes test-only. |
There was a problem hiding this comment.
LGTM — one-line RELEASE_AND_RETURN around a throwing tail call, matching the standard JSC pattern.
What was reviewed
- Confirmed
JSONParseis a throwing tail call under a liveDECLARE_THROW_SCOPE;RELEASE_AND_RETURNis the documented fix. - Checked sibling
JSONParsesites in this file (functionSamplingProfilerStackTraces,functionMemoryUsageStatistics) — different scope patterns, not the same class. - Test follows harness conventions:
{...bunEnv, ...}, concurrent pipe drain, combined-object assertion; on release buildsvalidateExceptionChecksis a no-op so it degrades to a smoke test rather than a false pass.
Extended reasoning...
Overview
One-line source change in src/jsc/modules/BunJSCModule.h: functionGenerateHeapSnapshotForDebugging now wraps its return JSValue::encode(JSONParse(...)) in RELEASE_AND_RETURN(scope, ...). A new subprocess test in test/js/bun/jsc/bun-jsc.test.ts spawns with BUN_JSC_validateExceptionChecks=1 and asserts exit 0 with ok object on stdout and no unchecked-scope lines on stderr.
Security risks
None. generateHeapSnapshotForDebugging is a debug-only introspection API in bun:jsc; the change is purely exception-scope bookkeeping and does not touch parsing of untrusted input, auth, crypto, or permissions.
Level of scrutiny
Low. This is the textbook JSC exception-check idiom — REVIEW.md explicitly states "Throwing tail calls go through RELEASE_AND_RETURN", and the PR description notes upstream jsc.cpp's functionGenerateHeapSnapshot/functionGenerateHeapSnapshotForGCDebugging use the identical pattern. The prior scope.releaseAssertNoException() covers the snapshot-builder region; the new macro releases the scope for the throwing JSONParse at the tail. There is no behavioral change on release builds.
Other factors
- No CODEOWNERS entry covers
src/jsc/modules/or this test file. - robobun verified locally: reproduced the exit-134 abort before, exit 0 after, full
bun-jsc.test.tssuite 37/0. - Test structure matches repo conventions:
bunExe()/bunEnvspread,await usingfor the process,Promise.allover stdout/stderr/exited, single combined-objectexpect. The stderr filter targets the validator's specific diagnostic lines rather than a broad substring, so unrelated debug-build noise won't flip it. - I looked at the two other
JSONParsecall sites in the same file for the "fix the whole class" rule:functionSamplingProfilerStackTracescomputes the result then callsscope.releaseAssertNoException()(asserts engine-generated JSON never throws — a different, intentional pattern), andfunctionMemoryUsageStatisticsdeclares no throw scope at all. Neither is the same shape as this fix, so scoping to the one function is correct. - The duplicate-PR bot flagged #34640; robobun already explained that PR carries this line as a drive-by inside a larger WebKit change with merge conflicts, and whichever lands first the other rebases cleanly.
|
Updated 6:59 PM PT - Aug 3rd, 2026
❌ @robobun, your commit 0f9b35c has 1 failures in
🧪 To try this PR locally: bunx bun-pr 36857That installs a local version of the PR into your bun-36857 --bun |
Repro
BUN_JSC_validateExceptionChecks=1 bun -e 'const s = require("bun:jsc").generateHeapSnapshotForDebugging(); console.log("ok", typeof s)'Exit 134 on debug/assert builds, deterministic. Release builds without assertions print
ok objectand exit 0.Cause
functionGenerateHeapSnapshotForDebuggingdeclares aDECLARE_THROW_SCOPE, callsscope.releaseAssertNoException()after building the snapshot JSON, then returnsJSValue::encode(JSONParse(globalObject, ...))without releasing the scope.JSONParsecallsLiteralParser::parseRecursivelywhich can throw, so the validator flags the scope as unchecked.Fix
Wrap the return in
RELEASE_AND_RETURN(scope, ...), matching the upstreamjsc.cppimplementation offunctionGenerateHeapSnapshot/functionGenerateHeapSnapshotForGCDebugging.Verification
Added a test in
test/js/bun/jsc/bun-jsc.test.tsthat spawns withBUN_JSC_validateExceptionChecks=1and asserts the snapshot returns an object with exit 0 and no unchecked-scope report. Fails before (exit 134, validator namesfunctionGenerateHeapSnapshotForDebugging), passes after.no test proof · iteration 1 · Platform-specific test(s) that do not run on this machine. Deferring to CI, which covers all platforms: test/js/bun/jsc/bun-jsc.test.ts