From bed16a331d99a305e32f0dcae30000ef69ea24ca Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Mon, 13 Jul 2026 22:05:04 +0000 Subject: [PATCH] JSC: tolerate pending TerminationException in property-lookup assertions DeferTermination throws the TerminationException from its destructor, after a property-slot lookup has already succeeded. JSObject::get already tolerates this (hasPendingTerminationException in its assertion); three sibling sites did not and abort assertion builds when terminate() races script execution: - JSValue::get (both overloads): same tolerance + RETURN_IF_EXCEPTION, hit via LLInt get_by_id during module evaluation - JSObject::getOwnPropertyDescriptor: same, hit via Object.defineProperty - JSModuleLoader::continueDynamicImport: assertNoException -> assertNoExceptionExceptTermination (rejectWithCaughtException cannot clear a termination, so it stays pending by design) No exception is ever cleared; callers' existing checks unwind as usual. Reproduced with a worker terminate() stress loop: 3/320 aborts before, 0/320 + 0/240 after. --- .../runtime/JSCJSValuePropertyInlines.h | 14 +++++++++++--- Source/JavaScriptCore/runtime/JSModuleLoader.cpp | 5 ++++- Source/JavaScriptCore/runtime/JSObject.cpp | 6 +++++- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/Source/JavaScriptCore/runtime/JSCJSValuePropertyInlines.h b/Source/JavaScriptCore/runtime/JSCJSValuePropertyInlines.h index 4b87b28e5d440..43a8c372ae718 100644 --- a/Source/JavaScriptCore/runtime/JSCJSValuePropertyInlines.h +++ b/Source/JavaScriptCore/runtime/JSCJSValuePropertyInlines.h @@ -46,9 +46,14 @@ ALWAYS_INLINE JSValue JSValue::get(JSGlobalObject* globalObject, PropertyName pr ALWAYS_INLINE JSValue JSValue::get(JSGlobalObject* globalObject, PropertyName propertyName, PropertySlot& slot) const { - auto scope = DECLARE_THROW_SCOPE(getVM(globalObject)); + VM& vm = getVM(globalObject); + auto scope = DECLARE_THROW_SCOPE(vm); bool hasSlot = getPropertySlot(globalObject, propertyName, slot); - EXCEPTION_ASSERT(!scope.exception() || !hasSlot); + // A DeferTermination scope unwinding inside the slot lookup can throw the + // TerminationException after the property was found; tolerate it like + // JSObject::get does and let the caller's exception check unwind. + EXCEPTION_ASSERT(!scope.exception() || vm.hasPendingTerminationException() || !hasSlot); + RETURN_IF_EXCEPTION(scope, jsUndefined()); if (!hasSlot) return jsUndefined(); RELEASE_AND_RETURN(scope, slot.getValue(globalObject, propertyName)); @@ -136,7 +141,10 @@ ALWAYS_INLINE JSValue JSValue::get(JSGlobalObject* globalObject, unsigned proper object = asObject(asCell()); bool hasSlot = object->getPropertySlot(globalObject, propertyName, slot); - EXCEPTION_ASSERT(!scope.exception() || !hasSlot); + // See JSValue::get(JSGlobalObject*, PropertyName, PropertySlot&) above: + // termination can be thrown after a successful lookup. + EXCEPTION_ASSERT(!scope.exception() || getVM(globalObject).hasPendingTerminationException() || !hasSlot); + RETURN_IF_EXCEPTION(scope, jsUndefined()); if (!hasSlot) return jsUndefined(); RELEASE_AND_RETURN(scope, slot.getValue(globalObject, propertyName)); diff --git a/Source/JavaScriptCore/runtime/JSModuleLoader.cpp b/Source/JavaScriptCore/runtime/JSModuleLoader.cpp index 4ebd937f1f9bb..5a5919525f06b 100644 --- a/Source/JavaScriptCore/runtime/JSModuleLoader.cpp +++ b/Source/JavaScriptCore/runtime/JSModuleLoader.cpp @@ -1023,7 +1023,10 @@ void JSModuleLoader::continueDynamicImport(JSGlobalObject* globalObject, ModuleL // 1.a. Perform ! Call(promiseCapability.[[Reject]], undefined, « moduleCompletion.[[Value]] »). promise->reject(vm, (*exception)->value()); // 1.b. Return UNUSED. - scope.assertNoException(); + // The abrupt completion may be a resolution failure caused by the + // TerminationException, which rejectWithCaughtException cannot clear; + // it stays pending in the VM and the caller's exception check unwinds. + scope.assertNoExceptionExceptTermination(); return; } // 2. Let module be moduleCompletion.[[Value]]. diff --git a/Source/JavaScriptCore/runtime/JSObject.cpp b/Source/JavaScriptCore/runtime/JSObject.cpp index 3bbbd19d3b61d..552c27a832dd9 100644 --- a/Source/JavaScriptCore/runtime/JSObject.cpp +++ b/Source/JavaScriptCore/runtime/JSObject.cpp @@ -3933,7 +3933,11 @@ bool JSObject::getOwnPropertyDescriptor(JSGlobalObject* globalObject, PropertyNa PropertySlot slot(this, PropertySlot::InternalMethodType::GetOwnProperty); bool result = methodTable()->getOwnPropertySlot(this, globalObject, propertyName, slot); - EXCEPTION_ASSERT_UNUSED(scope, !scope.exception() || !result); + // A DeferTermination scope unwinding inside the slot lookup can throw the + // TerminationException after the property was found; tolerate it like + // JSObject::get does and let the caller's exception check unwind. + EXCEPTION_ASSERT(!scope.exception() || vm.hasPendingTerminationException() || !result); + RETURN_IF_EXCEPTION(scope, false); if (!result) return false;