From f8af88adfdf9e54a52d7ce3febb18b1cf81f3319 Mon Sep 17 00:00:00 2001 From: Kevin Gibbons Date: Mon, 11 May 2026 10:55:35 -0700 Subject: [PATCH 1/3] deps: V8: cherry-pick 1a391f98cc7a MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Original commit message: [api] Deprecate kPromiseRejectAfterResolved and kPromiseResolveAfterResolved These events will be removed soon. Bug: 42213031 Change-Id: Ie70474ff33c40c7d9cb0c2d0fbe6b75da3c53a22 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7774767 Commit-Queue: Kevin Gibbons Reviewed-by: Olivier Flückiger Reviewed-by: Leszek Swirski Cr-Commit-Position: refs/heads/main@{#107395} Refs: https://github.com/v8/v8/commit/1a391f98cc7a9196369f2d6cab7df35ffbe92c08 --- common.gypi | 2 +- deps/v8/include/v8-promise.h | 6 ++++-- deps/v8/src/d8/d8.cc | 2 ++ deps/v8/src/runtime/runtime-promise.cc | 4 ++++ deps/v8/test/cctest/test-api.cc | 2 ++ deps/v8/test/inspector/isolate-data.cc | 13 +++++++++---- 6 files changed, 22 insertions(+), 7 deletions(-) diff --git a/common.gypi b/common.gypi index 36b4b1138dbfd2..cb2f22f0cedf89 100644 --- a/common.gypi +++ b/common.gypi @@ -40,7 +40,7 @@ # Reset this number to 0 on major V8 upgrades. # Increment by one for each non-official patch applied to deps/v8. - 'v8_embedder_string': '-node.21', + 'v8_embedder_string': '-node.22', ##### V8 defaults for Node.js ##### diff --git a/deps/v8/include/v8-promise.h b/deps/v8/include/v8-promise.h index 36412c774d1b51..3d104f6623831e 100644 --- a/deps/v8/include/v8-promise.h +++ b/deps/v8/include/v8-promise.h @@ -158,8 +158,10 @@ using PromiseHook = void (*)(PromiseHookType type, Local promise, enum PromiseRejectEvent { kPromiseRejectWithNoHandler = 0, kPromiseHandlerAddedAfterReject = 1, - kPromiseRejectAfterResolved = 2, - kPromiseResolveAfterResolved = 3, + kPromiseRejectAfterResolved V8_DEPRECATED("These events are being removed") = + 2, + kPromiseResolveAfterResolved V8_DEPRECATED("These events are being removed") = + 3, }; class PromiseRejectMessage { diff --git a/deps/v8/src/d8/d8.cc b/deps/v8/src/d8/d8.cc index 02b03480f166de..88f0da12d2f770 100644 --- a/deps/v8/src/d8/d8.cc +++ b/deps/v8/src/d8/d8.cc @@ -4575,11 +4575,13 @@ static void PrintMessageCallback(Local message, Local error) { void Shell::PromiseRejectCallback(v8::PromiseRejectMessage data) { if (options.ignore_unhandled_promises) return; + START_ALLOW_USE_DEPRECATED(); if (data.GetEvent() == v8::kPromiseRejectAfterResolved || data.GetEvent() == v8::kPromiseResolveAfterResolved) { // Ignore reject/resolve after resolved. return; } + END_ALLOW_USE_DEPRECATED(); v8::Local promise = data.GetPromise(); v8::Isolate* isolate = v8::Isolate::GetCurrent(); PerIsolateData* isolate_data = PerIsolateData::Get(isolate); diff --git a/deps/v8/src/runtime/runtime-promise.cc b/deps/v8/src/runtime/runtime-promise.cc index 262b9aa5aa6974..57b5604c43884b 100644 --- a/deps/v8/src/runtime/runtime-promise.cc +++ b/deps/v8/src/runtime/runtime-promise.cc @@ -34,8 +34,10 @@ RUNTIME_FUNCTION(Runtime_PromiseRejectAfterResolved) { HandleScope scope(isolate); DirectHandle promise = args.at(0); DirectHandle reason = args.at(1); + START_ALLOW_USE_DEPRECATED(); isolate->ReportPromiseReject(promise, reason, v8::kPromiseRejectAfterResolved); + END_ALLOW_USE_DEPRECATED(); return ReadOnlyRoots(isolate).undefined_value(); } @@ -44,8 +46,10 @@ RUNTIME_FUNCTION(Runtime_PromiseResolveAfterResolved) { HandleScope scope(isolate); DirectHandle promise = args.at(0); DirectHandle resolution = args.at(1); + START_ALLOW_USE_DEPRECATED(); isolate->ReportPromiseReject(promise, resolution, v8::kPromiseResolveAfterResolved); + END_ALLOW_USE_DEPRECATED(); return ReadOnlyRoots(isolate).undefined_value(); } diff --git a/deps/v8/test/cctest/test-api.cc b/deps/v8/test/cctest/test-api.cc index 8896acd700f829..174ec0f6af5fc3 100644 --- a/deps/v8/test/cctest/test-api.cc +++ b/deps/v8/test/cctest/test-api.cc @@ -16438,6 +16438,7 @@ void PromiseRejectCallback(v8::PromiseRejectMessage reject_message) { CHECK(reject_message.GetValue().IsEmpty()); break; } + START_ALLOW_USE_DEPRECATED(); case v8::kPromiseRejectAfterResolved: { promise_reject_after_resolved_counter++; break; @@ -16446,6 +16447,7 @@ void PromiseRejectCallback(v8::PromiseRejectMessage reject_message) { promise_resolve_after_resolved_counter++; break; } + END_ALLOW_USE_DEPRECATED(); } } diff --git a/deps/v8/test/inspector/isolate-data.cc b/deps/v8/test/inspector/isolate-data.cc index f067ba705a9490..6fefc1eb17c4fc 100644 --- a/deps/v8/test/inspector/isolate-data.cc +++ b/deps/v8/test/inspector/isolate-data.cc @@ -401,10 +401,15 @@ void InspectorIsolateData::PromiseRejectHandler(v8::PromiseRejectMessage data) { v8_inspector::StringView(reinterpret_cast(reason_str), strlen(reason_str))); return; - } else if (data.GetEvent() == v8::kPromiseRejectAfterResolved || - data.GetEvent() == v8::kPromiseResolveAfterResolved) { - // Ignore reject/resolve after resolved, like the blink handler. - return; + + } else { + START_ALLOW_USE_DEPRECATED(); + if (data.GetEvent() == v8::kPromiseRejectAfterResolved || + data.GetEvent() == v8::kPromiseResolveAfterResolved) { + // Ignore reject/resolve after resolved, like the blink handler. + return; + } + END_ALLOW_USE_DEPRECATED(); } v8::Local exception = data.GetValue(); From a244992c073128526e65de05a33bcf34661f06c7 Mon Sep 17 00:00:00 2001 From: Kevin Gibbons Date: Thu, 11 Jun 2026 13:29:34 -0700 Subject: [PATCH 2/3] deps: V8: cherry-pick 0cc9eb22c0b0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Original commit message: [api] Remove PromiseResolveAfterResolved and PromiseRejectAfterResolved These were previously deprecated in https://crrev.com/c/7774767 Bug: 42213031 Change-Id: I07b802b743bf052611f30a61c1132231df22f0bd Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7897881 Commit-Queue: Olivier Flückiger Reviewed-by: Camillo Bruni Reviewed-by: Olivier Flückiger Cr-Commit-Position: refs/heads/main@{#108040} Refs: https://github.com/v8/v8/commit/0cc9eb22c0b0d132344b83b906f8a0e5aaa7b61e --- common.gypi | 2 +- deps/v8/include/v8-promise.h | 6 ++--- .../builtins/promise-abstract-operations.tq | 10 ++------ deps/v8/src/d8/d8.cc | 7 ------ deps/v8/src/runtime/runtime-promise.cc | 24 ------------------- deps/v8/src/runtime/runtime.h | 2 -- deps/v8/test/cctest/test-api.cc | 16 ++++--------- deps/v8/test/inspector/isolate-data.cc | 8 ------- 8 files changed, 9 insertions(+), 66 deletions(-) diff --git a/common.gypi b/common.gypi index cb2f22f0cedf89..96fbb00f98577e 100644 --- a/common.gypi +++ b/common.gypi @@ -40,7 +40,7 @@ # Reset this number to 0 on major V8 upgrades. # Increment by one for each non-official patch applied to deps/v8. - 'v8_embedder_string': '-node.22', + 'v8_embedder_string': '-node.23', ##### V8 defaults for Node.js ##### diff --git a/deps/v8/include/v8-promise.h b/deps/v8/include/v8-promise.h index 3d104f6623831e..b1f85e6e50ec7e 100644 --- a/deps/v8/include/v8-promise.h +++ b/deps/v8/include/v8-promise.h @@ -158,10 +158,8 @@ using PromiseHook = void (*)(PromiseHookType type, Local promise, enum PromiseRejectEvent { kPromiseRejectWithNoHandler = 0, kPromiseHandlerAddedAfterReject = 1, - kPromiseRejectAfterResolved V8_DEPRECATED("These events are being removed") = - 2, - kPromiseResolveAfterResolved V8_DEPRECATED("These events are being removed") = - 3, + kDeprecatedPromiseRejectAfterResolved V8_DEPRECATED("Removed event") = 2, + kDeprecatedPromiseResolveAfterResolved V8_DEPRECATED("Removed event") = 3, }; class PromiseRejectMessage { diff --git a/deps/v8/src/builtins/promise-abstract-operations.tq b/deps/v8/src/builtins/promise-abstract-operations.tq index f50b5b75a0f326..d2de7eaab52006 100644 --- a/deps/v8/src/builtins/promise-abstract-operations.tq +++ b/deps/v8/src/builtins/promise-abstract-operations.tq @@ -12,12 +12,6 @@ extern transitioning runtime RejectPromise( extern transitioning runtime PromiseRevokeReject( implicit context: Context)(JSPromise): JSAny; -extern transitioning runtime PromiseRejectAfterResolved( - implicit context: Context)(JSPromise, JSAny): JSAny; - -extern transitioning runtime PromiseResolveAfterResolved( - implicit context: Context)(JSPromise, JSAny): JSAny; - extern transitioning runtime PromiseRejectEventFromStack( implicit context: Context)(JSPromise, JSAny): JSAny; } @@ -406,7 +400,7 @@ transitioning javascript builtin PromiseCapabilityDefaultReject( // 4. If alreadyResolved.[[Value]] is true, return undefined. if (alreadyResolved == True) { - return runtime::PromiseRejectAfterResolved(promise, reason); + return Undefined; } // 5. Set alreadyResolved.[[Value]] to true. @@ -434,7 +428,7 @@ transitioning javascript builtin PromiseCapabilityDefaultResolve( // 4. If alreadyResolved.[[Value]] is true, return undefined. if (alreadyResolved == True) { - return runtime::PromiseResolveAfterResolved(promise, resolution); + return Undefined; } // 5. Set alreadyResolved.[[Value]] to true. diff --git a/deps/v8/src/d8/d8.cc b/deps/v8/src/d8/d8.cc index 88f0da12d2f770..1c33ca83c3695f 100644 --- a/deps/v8/src/d8/d8.cc +++ b/deps/v8/src/d8/d8.cc @@ -4575,13 +4575,6 @@ static void PrintMessageCallback(Local message, Local error) { void Shell::PromiseRejectCallback(v8::PromiseRejectMessage data) { if (options.ignore_unhandled_promises) return; - START_ALLOW_USE_DEPRECATED(); - if (data.GetEvent() == v8::kPromiseRejectAfterResolved || - data.GetEvent() == v8::kPromiseResolveAfterResolved) { - // Ignore reject/resolve after resolved. - return; - } - END_ALLOW_USE_DEPRECATED(); v8::Local promise = data.GetPromise(); v8::Isolate* isolate = v8::Isolate::GetCurrent(); PerIsolateData* isolate_data = PerIsolateData::Get(isolate); diff --git a/deps/v8/src/runtime/runtime-promise.cc b/deps/v8/src/runtime/runtime-promise.cc index 57b5604c43884b..3c9d7184189915 100644 --- a/deps/v8/src/runtime/runtime-promise.cc +++ b/deps/v8/src/runtime/runtime-promise.cc @@ -29,30 +29,6 @@ RUNTIME_FUNCTION(Runtime_PromiseRejectEventFromStack) { return ReadOnlyRoots(isolate).undefined_value(); } -RUNTIME_FUNCTION(Runtime_PromiseRejectAfterResolved) { - DCHECK_EQ(2, args.length()); - HandleScope scope(isolate); - DirectHandle promise = args.at(0); - DirectHandle reason = args.at(1); - START_ALLOW_USE_DEPRECATED(); - isolate->ReportPromiseReject(promise, reason, - v8::kPromiseRejectAfterResolved); - END_ALLOW_USE_DEPRECATED(); - return ReadOnlyRoots(isolate).undefined_value(); -} - -RUNTIME_FUNCTION(Runtime_PromiseResolveAfterResolved) { - DCHECK_EQ(2, args.length()); - HandleScope scope(isolate); - DirectHandle promise = args.at(0); - DirectHandle resolution = args.at(1); - START_ALLOW_USE_DEPRECATED(); - isolate->ReportPromiseReject(promise, resolution, - v8::kPromiseResolveAfterResolved); - END_ALLOW_USE_DEPRECATED(); - return ReadOnlyRoots(isolate).undefined_value(); -} - RUNTIME_FUNCTION(Runtime_PromiseRevokeReject) { DCHECK_EQ(1, args.length()); HandleScope scope(isolate); diff --git a/deps/v8/src/runtime/runtime.h b/deps/v8/src/runtime/runtime.h index 8c5eac0907b305..ee6cc6f20608ec 100644 --- a/deps/v8/src/runtime/runtime.h +++ b/deps/v8/src/runtime/runtime.h @@ -441,8 +441,6 @@ constexpr bool CanTriggerGC(T... properties) { F(PromiseRevokeReject, 1, 1) \ F(RejectPromise, 3, 1) \ F(ResolvePromise, 2, 1) \ - F(PromiseRejectAfterResolved, 2, 1) \ - F(PromiseResolveAfterResolved, 2, 1) \ F(ConstructSuppressedError, 3, 1) \ F(ConstructAggregateErrorHelper, 4, 1) \ F(ConstructInternalAggregateErrorHelper, -1 /* <= 5*/, 1) diff --git a/deps/v8/test/cctest/test-api.cc b/deps/v8/test/cctest/test-api.cc index 174ec0f6af5fc3..ba7e079f411c1c 100644 --- a/deps/v8/test/cctest/test-api.cc +++ b/deps/v8/test/cctest/test-api.cc @@ -16381,8 +16381,6 @@ TEST(ErrorLevelWarning) { v8::PromiseRejectEvent reject_event = v8::kPromiseRejectWithNoHandler; int promise_reject_counter = 0; int promise_revoke_counter = 0; -int promise_reject_after_resolved_counter = 0; -int promise_resolve_after_resolved_counter = 0; int promise_reject_msg_line_number = -1; int promise_reject_msg_column_number = -1; int promise_reject_line_number = -1; @@ -16439,12 +16437,12 @@ void PromiseRejectCallback(v8::PromiseRejectMessage reject_message) { break; } START_ALLOW_USE_DEPRECATED(); - case v8::kPromiseRejectAfterResolved: { - promise_reject_after_resolved_counter++; + case v8::kDeprecatedPromiseRejectAfterResolved: { + // Unreachable break; } - case v8::kPromiseResolveAfterResolved: { - promise_resolve_after_resolved_counter++; + case v8::kDeprecatedPromiseResolveAfterResolved: { + // Unreachable break; } END_ALLOW_USE_DEPRECATED(); @@ -16470,8 +16468,6 @@ v8::Local RejectValue() { void ResetPromiseStates() { promise_reject_counter = 0; promise_revoke_counter = 0; - promise_reject_after_resolved_counter = 0; - promise_resolve_after_resolved_counter = 0; promise_reject_msg_line_number = -1; promise_reject_msg_column_number = -1; promise_reject_line_number = -1; @@ -16710,8 +16706,6 @@ TEST(PromiseRejectCallback) { CHECK(!GetPromise("v0")->HasHandler()); CHECK_EQ(0, promise_reject_counter); CHECK_EQ(0, promise_revoke_counter); - CHECK_EQ(1, promise_reject_after_resolved_counter); - CHECK_EQ(0, promise_resolve_after_resolved_counter); ResetPromiseStates(); @@ -16728,8 +16722,6 @@ TEST(PromiseRejectCallback) { CHECK(!GetPromise("y0")->HasHandler()); CHECK_EQ(1, promise_reject_counter); CHECK_EQ(0, promise_revoke_counter); - CHECK_EQ(0, promise_reject_after_resolved_counter); - CHECK_EQ(1, promise_resolve_after_resolved_counter); // Test stack frames. env.isolate()->SetCaptureStackTraceForUncaughtExceptions(true); diff --git a/deps/v8/test/inspector/isolate-data.cc b/deps/v8/test/inspector/isolate-data.cc index 6fefc1eb17c4fc..91a497463053df 100644 --- a/deps/v8/test/inspector/isolate-data.cc +++ b/deps/v8/test/inspector/isolate-data.cc @@ -402,14 +402,6 @@ void InspectorIsolateData::PromiseRejectHandler(v8::PromiseRejectMessage data) { strlen(reason_str))); return; - } else { - START_ALLOW_USE_DEPRECATED(); - if (data.GetEvent() == v8::kPromiseRejectAfterResolved || - data.GetEvent() == v8::kPromiseResolveAfterResolved) { - // Ignore reject/resolve after resolved, like the blink handler. - return; - } - END_ALLOW_USE_DEPRECATED(); } v8::Local exception = data.GetValue(); From 7e13073f938ea88c1e515ba0c007e4078122584b Mon Sep 17 00:00:00 2001 From: Kevin Gibbons Date: Fri, 19 Jun 2026 09:21:04 -0700 Subject: [PATCH 3/3] deps: V8: backport da20a197a7f9 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Original commit message: [builtins] Make Promise resolvers not keep resolved Promises alive Currently a Promise's resolve/reject functions unconditionally hold the Promise, plus a separate bit to track whether the Promise has been resolved. Instead, hold a single field with Promise|Undefined. This allows GC'ing a resolved Promise even if its resolvers are still alive. R=olivf@chromium.org Fixed: 42213031 Change-Id: Ice645dbabb79e63dfcac8ae843cad95439d7a1f1 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7646250 Reviewed-by: Darius Mercadier Commit-Queue: Kevin Gibbons Reviewed-by: Olivier Flückiger Cr-Commit-Position: refs/heads/main@{#108183} Refs: https://github.com/v8/v8/commit/da20a197a7f9c2045b1ee501a472072944a86332 Co-authored-by: Kevin Gibbons --- common.gypi | 2 +- deps/v8/src/builtins/builtins-promise.h | 8 +-- .../builtins/promise-abstract-operations.tq | 69 ++++++++++--------- deps/v8/src/builtins/promise-all.tq | 9 +-- deps/v8/src/compiler/js-call-reducer.cc | 6 +- deps/v8/src/execution/isolate.cc | 9 ++- .../test/cctest/test-code-stub-assembler.cc | 6 +- .../mjsunit/regress/regress-crbug-42213031.js | 26 +++++++ 8 files changed, 83 insertions(+), 52 deletions(-) create mode 100644 deps/v8/test/mjsunit/regress/regress-crbug-42213031.js diff --git a/common.gypi b/common.gypi index 96fbb00f98577e..d63f2d3ba71661 100644 --- a/common.gypi +++ b/common.gypi @@ -40,7 +40,7 @@ # Reset this number to 0 on major V8 upgrades. # Increment by one for each non-official patch applied to deps/v8. - 'v8_embedder_string': '-node.23', + 'v8_embedder_string': '-node.24', ##### V8 defaults for Node.js ##### diff --git a/deps/v8/src/builtins/builtins-promise.h b/deps/v8/src/builtins/builtins-promise.h index a775ea20411605..2f1ee5550940be 100644 --- a/deps/v8/src/builtins/builtins-promise.h +++ b/deps/v8/src/builtins/builtins-promise.h @@ -13,11 +13,9 @@ namespace internal { class PromiseBuiltins { public: enum PromiseResolvingFunctionContextSlot { - // The promise which resolve/reject callbacks fulfill. - kPromiseSlot = Context::MIN_CONTEXT_SLOTS, - - // Whether the callback was already invoked. - kAlreadyResolvedSlot, + // The promise which resolve/reject callbacks fulfill, or Undefined + // if already resolved. + kPromiseIfNotResolvedSlot = Context::MIN_CONTEXT_SLOTS, // Whether to trigger a debug event or not. Used in catch // prediction. diff --git a/deps/v8/src/builtins/promise-abstract-operations.tq b/deps/v8/src/builtins/promise-abstract-operations.tq index d2de7eaab52006..a1d246ee327549 100644 --- a/deps/v8/src/builtins/promise-abstract-operations.tq +++ b/deps/v8/src/builtins/promise-abstract-operations.tq @@ -265,8 +265,8 @@ const kPromiseCapabilitySize: type PromiseResolvingFunctionContext extends FunctionContext; extern enum PromiseResolvingFunctionContextSlot extends intptr constexpr 'PromiseBuiltins::PromiseResolvingFunctionContextSlot' { - kPromiseSlot: Slot, - kAlreadyResolvedSlot: Slot, + kPromiseIfNotResolvedSlot: + Slot, kDebugEventSlot: Slot, kPromiseContextLength } @@ -390,25 +390,29 @@ transitioning builtin NewPromiseCapability( transitioning javascript builtin PromiseCapabilityDefaultReject( js-implicit context: Context, receiver: JSAny)(reason: JSAny): JSAny { const context = %RawDownCast(context); - // 2. Let promise be F.[[Promise]]. - const promise = - *ContextSlot(context, PromiseResolvingFunctionContextSlot::kPromiseSlot); - - // 3. Let alreadyResolved be F.[[AlreadyResolved]]. - const alreadyResolved = *ContextSlot( - context, PromiseResolvingFunctionContextSlot::kAlreadyResolvedSlot); - - // 4. If alreadyResolved.[[Value]] is true, return undefined. - if (alreadyResolved == True) { - return Undefined; + // 2. Let promise be promiseOrEmpty.[[Value]]. + const promiseOrEmpty = + *ContextSlot( + context, PromiseResolvingFunctionContextSlot::kPromiseIfNotResolvedSlot); + + // 1. If promiseOrEmpty.[[Value]] is ~empty~, return undefined. + let promise: JSPromise; + typeswitch (promiseOrEmpty) { + case (Undefined): { + return Undefined; + } + case (p: JSPromise): { + promise = p; + } } - // 5. Set alreadyResolved.[[Value]] to true. + // 3. Set promiseOrEmpty.[[Value]] to ~empty~. *ContextSlot( - context, PromiseResolvingFunctionContextSlot::kAlreadyResolvedSlot) = - True; + context, PromiseResolvingFunctionContextSlot::kPromiseIfNotResolvedSlot) = + Undefined; - // 6. Return RejectPromise(promise, reason). + // 4. Perform RejectPromise(promise, reason). + // 5. Return undefined. const debugEvent = *ContextSlot( context, PromiseResolvingFunctionContextSlot::kDebugEventSlot); return RejectPromise(promise, reason, debugEvent); @@ -418,23 +422,26 @@ transitioning javascript builtin PromiseCapabilityDefaultReject( transitioning javascript builtin PromiseCapabilityDefaultResolve( js-implicit context: Context, receiver: JSAny)(resolution: JSAny): JSAny { const context = %RawDownCast(context); - // 2. Let promise be F.[[Promise]]. - const promise: JSPromise = - *ContextSlot(context, PromiseResolvingFunctionContextSlot::kPromiseSlot); - - // 3. Let alreadyResolved be F.[[AlreadyResolved]]. - const alreadyResolved: Boolean = *ContextSlot( - context, PromiseResolvingFunctionContextSlot::kAlreadyResolvedSlot); - - // 4. If alreadyResolved.[[Value]] is true, return undefined. - if (alreadyResolved == True) { - return Undefined; + // 2. Let promise be promiseOrEmpty.[[Value]]. + const promiseOrEmpty = + *ContextSlot( + context, PromiseResolvingFunctionContextSlot::kPromiseIfNotResolvedSlot); + + // 1. If promiseOrEmpty.[[Value]] is ~empty~, return undefined. + let promise: JSPromise; + typeswitch (promiseOrEmpty) { + case (Undefined): { + return Undefined; + } + case (p: JSPromise): { + promise = p; + } } - // 5. Set alreadyResolved.[[Value]] to true. + // 3. Set promiseOrEmpty.[[Value]] to ~empty~. *ContextSlot( - context, PromiseResolvingFunctionContextSlot::kAlreadyResolvedSlot) = - True; + context, PromiseResolvingFunctionContextSlot::kPromiseIfNotResolvedSlot) = + Undefined; // The rest of the logic (and the catch prediction) is // encapsulated in the dedicated ResolvePromise builtin. diff --git a/deps/v8/src/builtins/promise-all.tq b/deps/v8/src/builtins/promise-all.tq index b45d55bba5ab4b..440866706f5c0b 100644 --- a/deps/v8/src/builtins/promise-all.tq +++ b/deps/v8/src/builtins/promise-all.tq @@ -63,17 +63,14 @@ macro CreatePromiseResolvingFunctionsContext( nativeContext, PromiseResolvingFunctionContextSlot::kPromiseContextLength)); InitContextSlot( - resolveContext, PromiseResolvingFunctionContextSlot::kPromiseSlot, - promise); - InitContextSlot( - resolveContext, PromiseResolvingFunctionContextSlot::kAlreadyResolvedSlot, - False); + resolveContext, + PromiseResolvingFunctionContextSlot::kPromiseIfNotResolvedSlot, promise); InitContextSlot( resolveContext, PromiseResolvingFunctionContextSlot::kDebugEventSlot, debugEvent); static_assert( PromiseResolvingFunctionContextSlot::kPromiseContextLength == - ContextSlot::MIN_CONTEXT_SLOTS + 3); + ContextSlot::MIN_CONTEXT_SLOTS + 2); return resolveContext; } diff --git a/deps/v8/src/compiler/js-call-reducer.cc b/deps/v8/src/compiler/js-call-reducer.cc index ececdd1d7e95b0..e9f8a024edd6de 100644 --- a/deps/v8/src/compiler/js-call-reducer.cc +++ b/deps/v8/src/compiler/js-call-reducer.cc @@ -2543,10 +2543,8 @@ TNode PromiseBuiltinReducerAssembler::ReducePromiseConstructor( // Allocate a promise context for the closures below. TNode promise_context = CreateFunctionContext( native_context, context, PromiseBuiltins::kPromiseContextLength); - StoreContextNoCellSlot(promise_context, PromiseBuiltins::kPromiseSlot, - promise); - StoreContextNoCellSlot(promise_context, PromiseBuiltins::kAlreadyResolvedSlot, - FalseConstant()); + StoreContextNoCellSlot(promise_context, + PromiseBuiltins::kPromiseIfNotResolvedSlot, promise); StoreContextNoCellSlot(promise_context, PromiseBuiltins::kDebugEventSlot, TrueConstant()); diff --git a/deps/v8/src/execution/isolate.cc b/deps/v8/src/execution/isolate.cc index cebff6c533a49e..06fa14bf6b1002 100644 --- a/deps/v8/src/execution/isolate.cc +++ b/deps/v8/src/execution/isolate.cc @@ -1253,9 +1253,12 @@ void CaptureAsyncStackTrace(Isolate* isolate, DirectHandle promise, DirectHandle function( Cast(reaction->fulfill_handler()), isolate); DirectHandle context(function->context(), isolate); - promise = direct_handle( - Cast(context->GetNoCell(PromiseBuiltins::kPromiseSlot)), - isolate); + Tagged promise_or_undefined = + context->GetNoCell(PromiseBuiltins::kPromiseIfNotResolvedSlot); + if (!TryCast(direct_handle(promise_or_undefined, isolate), &promise)) { + DCHECK(IsUndefined(promise_or_undefined)); + return; + } } else { // We have some generic promise chain here, so try to // continue with the chained promise on the reaction diff --git a/deps/v8/test/cctest/test-code-stub-assembler.cc b/deps/v8/test/cctest/test-code-stub-assembler.cc index 32e8dc1b32c59c..3d8501a08bf299 100644 --- a/deps/v8/test/cctest/test-code-stub-assembler.cc +++ b/deps/v8/test/cctest/test-code-stub-assembler.cc @@ -3037,7 +3037,8 @@ TEST(CreatePromiseResolvingFunctionsContext) { DirectHandle context_js = Cast(result); CHECK_EQ(isolate->root(RootIndex::kEmptyScopeInfo), context_js->scope_info()); CHECK_EQ(*isolate->native_context(), context_js->native_context()); - CHECK(IsJSPromise(context_js->GetNoCell(PromiseBuiltins::kPromiseSlot))); + CHECK(IsJSPromise( + context_js->GetNoCell(PromiseBuiltins::kPromiseIfNotResolvedSlot))); CHECK_EQ(ReadOnlyRoots(isolate).false_value(), context_js->GetNoCell(PromiseBuiltins::kDebugEventSlot)); } @@ -3246,7 +3247,8 @@ TEST(NewPromiseCapability) { CHECK_EQ(*isolate->native_context(), callback_context->native_context()); CHECK_EQ(PromiseBuiltins::kPromiseContextLength, callback_context->length()); - CHECK_EQ(callback_context->GetNoCell(PromiseBuiltins::kPromiseSlot), + CHECK_EQ(callback_context->GetNoCell( + PromiseBuiltins::kPromiseIfNotResolvedSlot), result->promise()); } } diff --git a/deps/v8/test/mjsunit/regress/regress-crbug-42213031.js b/deps/v8/test/mjsunit/regress/regress-crbug-42213031.js new file mode 100644 index 00000000000000..7bace045b021f0 --- /dev/null +++ b/deps/v8/test/mjsunit/regress/regress-crbug-42213031.js @@ -0,0 +1,26 @@ +// Copyright 2026 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Flags: --expose-gc + +const pending = new Promise(() => {}); + +(async function () { + let wr; + + await (async function () { + const payload = { }; + wr = new WeakRef(payload); + const resolved = Promise.resolve(payload); + // The pending Promise should not prevent GC of the race Promise once the race settles. + await Promise.race([pending, resolved]); + })(); + + await gc({ type: 'major', execution: 'async' }); + + assertEquals(undefined, wr.deref()); +})().catch((e) => { + console.error(e); + quit(1); +});