bun:test: check exception after isArray() in expect.any(Array), toMatchObject, toHaveProperty - #34753
bun:test: check exception after isArray() in expect.any(Array), toMatchObject, toHaveProperty#34753robobun wants to merge 1 commit into
Conversation
|
Updated 8:21 PM PT - Jul 19th, 2026
❌ @robobun, your commit 4b23b02 has 1 failures in
🧪 To try this PR locally: bunx bun-pr 34753That installs a local version of the PR into your bun-34753 --bun |
WalkthroughChangesMatcher exception safety
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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 `@test/js/bun/test/expect.test.js`:
- Around line 681-683: Replace the explanatory regression-test comment near
expect.any(Array), toMatchObject, and toHaveProperty with only the relevant
issue URL, preserving the test behavior and removing all other comment text.
- Around line 684-711: Update the child script in the Proxy matcher test so
successful matcher calls remain uncaught, while expected failures are explicitly
asserted with their error messages. Add revoked-Proxy coverage for the
toMatchObject and toHaveProperty paths in addition to the existing toEqual case,
and ensure the assertions validate the expected outcomes rather than swallowing
all exceptions.
🪄 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: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: f66c3c7e-4ee5-4087-b432-c6219444fe95
📒 Files selected for processing (2)
src/jsc/bindings/bindings.cpptest/js/bun/test/expect.test.js
…chObject, toHaveProperty JSC::isArray() declares a throw scope on its Proxy path (isArraySlowInline). Three call sites in bindings.cpp used it inside an if condition and then either entered another throw-scope-declaring call or returned with the outer ThrowScope still unsatisfied, which aborts under the JSC exception-scope validator: matchAsymmetricMatcherAndGetFlags: isArray then hasInstance Bun__deepMatch: two chained isArray calls then getPropertyNames JSC__JSValue__getIfPropertyExistsFromPath: isArray false then return Hoist each isArray() result out of the condition and RETURN_IF_EXCEPTION immediately after.
a8a0538 to
4b23b02
Compare
There was a problem hiding this comment.
LGTM — mechanical RETURN_IF_EXCEPTION insertion after three JSC::isArray() call sites, matching the pattern already used at the Bun__deepEquals sites in the same file.
What was reviewed
- Confirmed each site has a
ThrowScopein scope (throwScopeparam for the first two, localscopefor the third) and the sentinel return values match sibling error paths. Bun__deepMatchnow evaluates bothisArraycalls unconditionally instead of short-circuiting; verified this can't introduce a new throw becausesubsetObj->getPropertyNames()already runs (and throws for a revoked Proxy) before that point.- Test drains stdout/stderr/exited concurrently, spreads
bunEnv, and assertssignalCode: nullso a validator abort can't be masked by the innertry/catch. - Both CodeRabbit findings were responded to by the author and withdrawn.
Extended reasoning...
Overview
Three call sites in src/jsc/bindings/bindings.cpp used JSC::isArray() inline in an if condition without checking for an exception afterward. isArray() declares a throw scope on its Proxy path (isArraySlowInline), so under BUN_JSC_validateExceptionChecks=1 the next throw-scope-declaring call (or scope destruction) aborts. The fix hoists each result into a local and inserts RETURN_IF_EXCEPTION immediately after — the exact same shape already used for the v1Array/v2Array sites in Bun__deepEquals in this file, and the same fix applied to NodeVM.cpp in #34747. A subprocess test in expect.test.js runs each affected matcher shape under the validator flag and asserts clean exit.
Security risks
None. This is test-runner matcher code (expect.any, toMatchObject, toHaveProperty) with no auth, crypto, network, or filesystem exposure. The change adds exception checks; it removes nothing.
Level of scrutiny
Low-to-moderate. The transformation is purely mechanical and pattern-matched against neighboring code. I verified: (1) matchAsymmetricMatcherAndGetFlags receives ThrowScope& throwScope by reference and already returns AsymmetricMatcherResult::FAIL on other error paths; (2) Bun__deepMatch receives ThrowScope& throwScope and returns false on exception at the adjacent getPropertyNames check; (3) JSC__JSValue__getIfPropertyExistsFromPath declares a local ThrowScope scope and returns {} on exception throughout. The one behavioral delta — Bun__deepMatch no longer short-circuits the second isArray — is safe because subsetObj->getPropertyNames at line 1645 would already have thrown for any subsetValue that could make the second isArray throw.
Other factors
The robobun evidence gate shows fail-before/pass-after on both the ASAN debug build and a release build, and the author reports expect.test.js (408 pass), jest-extended.test.js, and deep-equal.test.ts all passing. CodeRabbit raised two findings on the test (comment style and try/catch swallowing outcomes); the author explained the test's scope is the validator abort — not matcher semantics, which are under active change in #32948/#34649 — and both were withdrawn. No prior reviews from me on this PR.
|
Found 1 issue this PR may fix:
🤖 Generated with Claude Code |
|
Build 75937: |
Repro
Under
BUN_JSC_validateExceptionChecks=1on a debug build:Cause
JSC::isArray()declares a throw scope on its Proxy path (isArraySlowInline, for the revoked-Proxy case). Three call sites insrc/jsc/bindings/bindings.cppused it inside anifcondition and then either entered another throw-scope-declaring call, or returned with the scope'sm_needExceptionCheckstill set:matchAsymmetricMatcherAndGetFlags(AsymmetricMatcherConstructorType::Array):isArray()then falls through toconstructorObject->hasInstance().Bun__deepMatch:isArray(globalObject, objValue) && isArray(globalObject, subsetValue)chains two throw-scope calls, thengetPropertyNames().JSC__JSValue__getIfPropertyExistsFromPath(thetoHavePropertypath lookup): whenisArray()returns false for a Proxy over an iterable non-array (Set, Map, generator), the body is skipped and the function returns{}with the outerThrowScopestill unsatisfied.The
Bun__deepEqualssites (v1Array/v2Array) already had the check.Fix
Hoist each
isArray()result out of the condition andRETURN_IF_EXCEPTIONimmediately after it. Same pattern as #34747 for theNodeVM.cppinstances.Release behavior is effectively unchanged for
expect.any(Array)andtoHaveProperty(theisArrayrevocation error already reached the caller via a later check). FortoMatchObjectwith a revoked-Proxy receiver, the thrownTypeErrormessage changes from the generic revocation message toArray.isArray cannot be called on a Proxy that has been revoked, matching the operation that actually failed.Verification
New subprocess test in
test/js/bun/test/expect.test.jsspawns withBUN_JSC_validateExceptionChecks=1and runs every affected matcher shape with transparent and revoked Proxies. On builds without exception-scope verification the option is a no-op and the child exits 0 either way.expect.test.js(408 pass),jest-extended.test.js(57 pass),test/js/node/assert/deep-equal.test.ts(260 pass) all pass.Related
#32948 applies the same
Bun__deepMatchsplit as part of a larger Proxy-transparency change and composes with this one; it does not touch theexpect.any(Array)ortoHavePropertysites.[stamp-90s] gate passed · iteration 1 · 2 files touched
fails on main (without fix)
passes on PR (with fix)
diff hotspot
gate history · 2 passed · 0 rejected · iteration 1
evidence per changed file