util.isError: propagate a throwing getPrototypeOf trap instead of crashing
Closed
RO
3mo ago+50+50+01234567890123456789-12-12-01234567890123456789
Problem
util.isError(new Proxy({}, { getPrototypeOf() { throw err; } }))crashes the process: release builds withSegmentation fault at address 0x0, debug builds with an abort, instead of throwingerrthe wayerr instanceof Error(what Node'sutil.isErroris built on) does.- Cause, in
jsFunctionIsError(src/jsc/modules/NodeUtilTypesModule.cpp):- the
PropertySlotcreated withInternalMethodType::VMInquiryfor theSymbol.toStringTagcheck stays alive until the end of the function, and a VMInquiry slot forbids entering the VM for as long as it exists. Theobject->getPrototype(globalObject)call further down runs the Proxy'sgetPrototypeOftrap, so in debug builds the VM entry check aborts; in release builds the trap is not run at all, the call yieldsundefined, and the Proxy turns that into aTypeError(so even a well-behaved trap madeutil.isErrorfail). - the value returned by
getPrototype()is used without checking for that exception. A throwinggetPrototype()returns the emptyJSValue, which passesisCell(), soproto.inherits<ErrorInstance>()reads the structure of a null cell.
- the
Fix
- Scope the VMInquiry slot to the
toStringTagcheck so it is destroyed beforegetPrototype()is called, and addRETURN_IF_EXCEPTIONaftergetPrototype()so the trap's error propagates to the caller. - This matches Node, where
util.isError(x)ends inx instanceof Error, which runs the same trap and throws the same error. - Verified with
test/js/node/util/util.test.js,util > isError > handles Proxy getPrototypeOf traps(spawned child): the thrown error is the trap's error, and non-throwing traps returningError.prototype/nullgivetrue/false. Without the fix the child exits 139 (release) or 134 (debug); with it the test passes and the rest ofutil.test.js(209 tests) still passes.
Background
PropertySlotwithInternalMethodType::VMInquiryasks JSC for a side-effect-free lookup; JSC enforces that by holding aDisallowVMEntrytoken for the slot's lifetime, which makes any attempt to run JavaScript while the slot is alive fail (assert in debug, aTypeError-style failure in release).- A JSC exception is pending state on the VM: a function that threw returns a placeholder (here the empty
JSValue) and the caller must check the scope before using the result.
History
This PR originally also fixed the stale-exception / getPrototype crash in the Bun.inspect property walk, the matching check in napi_get_all_property_names, the util.inspect lazy properties, and the BunObject.cpp debug-only report. Those are consolidated in #29642 (property walk, napi, BunObject.cpp) and #39382 (util.inspect lazy properties); the windowsEnv change only worked around the stale-structure assertion that #37001 fixes in JSC. This PR has been narrowed to the util.isError fix, which none of those cover. The same util.isError fix was also part of #37175 and #39412, both closed.