diff --git a/src/bun.js/bindings/VM.zig b/src/bun.js/bindings/VM.zig index cc29608cf0..4cd9d2747d 100644 --- a/src/bun.js/bindings/VM.zig +++ b/src/bun.js/bindings/VM.zig @@ -160,7 +160,12 @@ pub const VM = opaque { var scope: bun.jsc.ExceptionValidationScope = undefined; scope.init(global_object, @src()); defer scope.deinit(); - scope.assertNoException(); + // If there's already an exception pending (e.g., from a caught stack overflow + // in JavaScript that wasn't fully cleared), just return the error without + // asserting or throwing a new exception. The existing exception will propagate. + if (scope.hasExceptionOrFalseWhenAssertionsAreDisabled()) { + return error.JSError; + } JSC__VM__throwError(vm, global_object, value); scope.assertExceptionPresenceMatches(true); return error.JSError; diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp index 945c31bda4..b5555588e0 100644 --- a/src/bun.js/bindings/bindings.cpp +++ b/src/bun.js/bindings/bindings.cpp @@ -1716,6 +1716,12 @@ template inline bool deepEqualsWrapperImpl(JSC::EncodedJSValue a, JSC::EncodedJSValue b, JSC::JSGlobalObject* global) { auto& vm = global->vm(); + // If there's already an exception pending (e.g., from a caught stack overflow), + // return false immediately to avoid triggering assertion failures when creating + // a ThrowScope. The caller (fromJSHostCallGeneric in Zig) has a CatchScope that + // will handle propagating the exception. + if (vm.exceptionForInspection()) + return false; auto scope = DECLARE_THROW_SCOPE(vm); Vector, 16> stack; MarkedArgumentBuffer args; @@ -2636,6 +2642,10 @@ size_t JSC__VM__heapSize(JSC::VM* arg0) bool JSC__JSValue__isStrictEqual(JSC::EncodedJSValue l, JSC::EncodedJSValue r, JSC::JSGlobalObject* globalObject) { auto& vm = globalObject->vm(); + // If there's already an exception pending, return false immediately to avoid + // triggering assertion failures when creating a ThrowScope. + if (vm.exceptionForInspection()) + return false; auto scope = DECLARE_THROW_SCOPE(vm); RELEASE_AND_RETURN(scope, JSC::JSValue::strictEqual(globalObject, JSC::JSValue::decode(l), JSC::JSValue::decode(r))); } @@ -2672,10 +2682,16 @@ bool JSC__JSValue__jestStrictDeepEquals(JSC::EncodedJSValue JSValue0, JSC::Encod bool JSC__JSValue__jestDeepMatch(JSC::EncodedJSValue JSValue0, JSC::EncodedJSValue JSValue1, JSC::JSGlobalObject* globalObject, bool replacePropsWithAsymmetricMatchers) { + auto& vm = globalObject->vm(); + // If there's already an exception pending, return false immediately to avoid + // triggering assertion failures when creating a ThrowScope. + if (vm.exceptionForInspection()) + return false; + JSValue obj = JSValue::decode(JSValue0); JSValue subset = JSValue::decode(JSValue1); - ThrowScope scope = DECLARE_THROW_SCOPE(globalObject->vm()); + ThrowScope scope = DECLARE_THROW_SCOPE(vm); std::set objVisited; std::set subsetVisited;