From 6e83cef951dceec7d82a221d1b2121942a049c0c Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Sat, 25 Jul 2026 02:50:37 +0000 Subject: [PATCH] Promise.any: give the AggregateError a stack, message, and no spurious cause The five Promise.any rejection sites in JSPromiseConstructor.cpp and JSMicrotask.cpp called createAggregateError with message = String() and cause = jsUndefined(). ErrorInstance::finishCreation treats a non-empty JSValue cause as an explicit cause and installs it as an own property, so every Promise.any rejection carried an own 'cause: undefined'. Route all five call sites through createAggregateErrorForPromiseAnyRejection, which passes an empty JSValue for cause and (under BUN_JSC_ADDITIONS) V8's "All promises were rejected" message. The async rejection paths run from a native internal microtask with no JS frames on the stack, so the captured trace is empty and materializeErrorInfoIfNeeded's BUN_JSC_ADDITIONS branch (gated on !m_stackTrace->isEmpty()) never ran, leaving .stack undefined. Drop the isEmpty() gate so the embedder hook still produces the "Name: message" header for zero-frame errors; skip line/column/sourceURL when there are no frames to source them from. --- Source/JavaScriptCore/runtime/AggregateError.h | 10 ++++++++++ Source/JavaScriptCore/runtime/ErrorInstance.cpp | 13 ++++++++----- Source/JavaScriptCore/runtime/JSMicrotask.cpp | 2 +- .../JavaScriptCore/runtime/JSPromiseConstructor.cpp | 8 ++++---- 4 files changed, 23 insertions(+), 10 deletions(-) diff --git a/Source/JavaScriptCore/runtime/AggregateError.h b/Source/JavaScriptCore/runtime/AggregateError.h index 269de59c20994..fedb1f48dd43f 100644 --- a/Source/JavaScriptCore/runtime/AggregateError.h +++ b/Source/JavaScriptCore/runtime/AggregateError.h @@ -31,4 +31,14 @@ namespace JSC { ErrorInstance* createAggregateError(VM&, Structure*, JSArray* errors, const String& message, JSValue cause, ErrorInstance::SourceAppender = nullptr, RuntimeType = TypeNothing, bool useCurrentFrame = true); +inline ErrorInstance* createAggregateErrorForPromiseAnyRejection(VM& vm, Structure* structure, JSArray* errors) +{ +#if USE(BUN_JSC_ADDITIONS) + String message = "All promises were rejected"_s; +#else + String message; +#endif + return createAggregateError(vm, structure, errors, message, JSValue()); +} + } // namespace JSC diff --git a/Source/JavaScriptCore/runtime/ErrorInstance.cpp b/Source/JavaScriptCore/runtime/ErrorInstance.cpp index 8cb916db573db..a2173070a05d0 100644 --- a/Source/JavaScriptCore/runtime/ErrorInstance.cpp +++ b/Source/JavaScriptCore/runtime/ErrorInstance.cpp @@ -407,7 +407,8 @@ bool ErrorInstance::materializeErrorInfoIfNeeded(VM& vm) #if USE(BUN_JSC_ADDITIONS) auto& fn = vm.onComputeErrorInfoJSValue(); - if (fn && m_stackTrace && !m_stackTrace->isEmpty()) { + if (fn && m_stackTrace) { + bool hasFrames = !m_stackTrace->isEmpty(); m_errorInfoMaterialized = true; DeferGCForAWhile deferGC(vm); @@ -424,10 +425,12 @@ bool ErrorInstance::materializeErrorInfoIfNeeded(VM& vm) auto attributes = static_cast(PropertyAttribute::DontEnum); - putDirect(vm, vm.propertyNames->line, jsNumber(m_lineColumn.line), attributes); - putDirect(vm, vm.propertyNames->column, jsNumber(m_lineColumn.column), attributes); - if (!m_sourceURL.isEmpty()) - putDirect(vm, vm.propertyNames->sourceURL, jsString(vm, WTF::move(m_sourceURL)), attributes); + if (hasFrames) { + putDirect(vm, vm.propertyNames->line, jsNumber(m_lineColumn.line), attributes); + putDirect(vm, vm.propertyNames->column, jsNumber(m_lineColumn.column), attributes); + if (!m_sourceURL.isEmpty()) + putDirect(vm, vm.propertyNames->sourceURL, jsString(vm, WTF::move(m_sourceURL)), attributes); + } if (!m_stackPropertyAlreadyMaterialized) putDirect(vm, vm.propertyNames->stack, stack, attributes); diff --git a/Source/JavaScriptCore/runtime/JSMicrotask.cpp b/Source/JavaScriptCore/runtime/JSMicrotask.cpp index 2d5a83dcac3f0..c3a4220b1c3f2 100644 --- a/Source/JavaScriptCore/runtime/JSMicrotask.cpp +++ b/Source/JavaScriptCore/runtime/JSMicrotask.cpp @@ -447,7 +447,7 @@ static void promiseAnyResolveJob(JSGlobalObject* globalObject, VM& vm, JSPromise globalContext->setRemainingElementsCount(count); if (!count) { auto* promise = uncheckedDowncast(globalContext->promise()); - auto* aggregateError = createAggregateError(vm, globalObject->errorStructure(ErrorType::AggregateError), errors, String(), jsUndefined()); + auto* aggregateError = createAggregateErrorForPromiseAnyRejection(vm, globalObject->errorStructure(ErrorType::AggregateError), errors); scope.release(); promise->reject(vm, aggregateError); } diff --git a/Source/JavaScriptCore/runtime/JSPromiseConstructor.cpp b/Source/JavaScriptCore/runtime/JSPromiseConstructor.cpp index 6d818dcb2c218..ffef1f5c528e5 100644 --- a/Source/JavaScriptCore/runtime/JSPromiseConstructor.cpp +++ b/Source/JavaScriptCore/runtime/JSPromiseConstructor.cpp @@ -1224,7 +1224,7 @@ static JSObject* promiseAnySlow(JSGlobalObject* globalObject, CallFrame* callFra uint64_t count = globalContext->remainingElementsCount() - 1; globalContext->setRemainingElementsCount(count); if (!count) { - auto* aggregateError = createAggregateError(vm, globalObject->errorStructure(ErrorType::AggregateError), errors, String(), jsUndefined()); + auto* aggregateError = createAggregateErrorForPromiseAnyRejection(vm, globalObject->errorStructure(ErrorType::AggregateError), errors); callReject(aggregateError); if (scope.exception()) [[unlikely]] { callRejectWithScopeException(); @@ -1334,7 +1334,7 @@ JSC_DEFINE_HOST_FUNCTION(promiseConstructorFuncAny, (JSGlobalObject* globalObjec uint64_t count = globalContext->remainingElementsCount() - 1; globalContext->setRemainingElementsCount(count); if (!count) { - auto* aggregateError = createAggregateError(vm, globalObject->errorStructure(ErrorType::AggregateError), errors, String(), jsUndefined()); + auto* aggregateError = createAggregateErrorForPromiseAnyRejection(vm, globalObject->errorStructure(ErrorType::AggregateError), errors); scope.release(); promise->reject(vm, aggregateError); if (scope.exception()) [[unlikely]] { @@ -1371,7 +1371,7 @@ JSC_DEFINE_HOST_FUNCTION(promiseAnyRejectFunction, (JSGlobalObject* globalObject uint64_t count = globalContext->remainingElementsCount() - 1; globalContext->setRemainingElementsCount(count); if (!count) { - auto* aggregateError = createAggregateError(vm, globalObject->errorStructure(ErrorType::AggregateError), errors, String(), jsUndefined()); + auto* aggregateError = createAggregateErrorForPromiseAnyRejection(vm, globalObject->errorStructure(ErrorType::AggregateError), errors); scope.release(); promise->reject(vm, aggregateError); } @@ -1406,7 +1406,7 @@ JSC_DEFINE_HOST_FUNCTION(promiseAnySlowRejectFunction, (JSGlobalObject* globalOb uint64_t count = globalContext->remainingElementsCount() - 1; globalContext->setRemainingElementsCount(count); if (!count) { - auto* aggregateError = createAggregateError(vm, globalObject->errorStructure(ErrorType::AggregateError), errors, String(), jsUndefined()); + auto* aggregateError = createAggregateErrorForPromiseAnyRejection(vm, globalObject->errorStructure(ErrorType::AggregateError), errors); MarkedArgumentBuffer rejectArguments; rejectArguments.append(aggregateError); ASSERT(!rejectArguments.hasOverflowed());