node/deps/v8/src/builtins/builtins-async-disposable-stack.cc
Michaël Zasso 8db664f72c
deps: V8: cherry-pick 1d7159580156
Original commit message:

    [explicit-resource-management] Clear isolate internal exception

    This CL clears the isolate internal exception before rejecting
    the promise.

    Bug: 418103036
    Change-Id: If3748f7fc4b79b7b5be5009b9fff0d0267541a6f
    Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/6594252
    Reviewed-by: Shu-yu Guo <syg@chromium.org>
    Commit-Queue: Rezvan Mahdavi Hezaveh <rezvan@chromium.org>
    Cr-Commit-Position: refs/heads/main@{#100532}

Refs: 1d71595801
PR-URL: https://github.com/nodejs/node/pull/58749
Reviewed-By: Richard Lau <richard.lau@ibm.com>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
2025-07-03 17:37:17 +02:00

408 lines
16 KiB
C++

// Copyright 2024 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.
#include "src/api/api.h"
#include "src/base/logging.h"
#include "src/base/macros.h"
#include "src/builtins/builtins-utils-inl.h"
#include "src/builtins/builtins.h"
#include "src/execution/isolate.h"
#include "src/handles/maybe-handles.h"
#include "src/objects/heap-object.h"
#include "src/objects/js-disposable-stack-inl.h"
#include "src/objects/js-disposable-stack.h"
#include "src/objects/js-objects.h"
#include "src/objects/js-promise-inl.h"
#include "src/objects/js-promise.h"
#include "src/objects/objects.h"
#include "src/roots/roots.h"
namespace v8 {
namespace internal {
BUILTIN(AsyncDisposableStackOnFulfilled) {
HandleScope scope(isolate);
DirectHandle<JSDisposableStackBase> stack(
Cast<
JSDisposableStackBase>(isolate->context()->GetNoCell(static_cast<int>(
JSDisposableStackBase::AsyncDisposableStackContextSlots::kStack))),
isolate);
DirectHandle<JSPromise> promise(
Cast<JSPromise>(isolate->context()->GetNoCell(static_cast<int>(
JSDisposableStackBase::AsyncDisposableStackContextSlots::
kOuterPromise))),
isolate);
MAYBE_RETURN(JSAsyncDisposableStack::NextDisposeAsyncIteration(isolate, stack,
promise),
ReadOnlyRoots(isolate).exception());
return ReadOnlyRoots(isolate).undefined_value();
}
BUILTIN(AsyncDisposableStackOnRejected) {
HandleScope scope(isolate);
DirectHandle<JSDisposableStackBase> stack(
Cast<
JSDisposableStackBase>(isolate->context()->GetNoCell(static_cast<int>(
JSDisposableStackBase::AsyncDisposableStackContextSlots::kStack))),
isolate);
DirectHandle<JSPromise> promise(
Cast<JSPromise>(isolate->context()->GetNoCell(static_cast<int>(
JSDisposableStackBase::AsyncDisposableStackContextSlots::
kOuterPromise))),
isolate);
DirectHandle<Object> rejection_error = args.at(1);
// (TODO:rezvan): Pass the correct pending message.
DirectHandle<Object> message(isolate->pending_message(), isolate);
DCHECK(isolate->is_catchable_by_javascript(*rejection_error));
JSDisposableStackBase::HandleErrorInDisposal(isolate, stack, rejection_error,
message);
MAYBE_RETURN(JSAsyncDisposableStack::NextDisposeAsyncIteration(isolate, stack,
promise),
ReadOnlyRoots(isolate).exception());
return ReadOnlyRoots(isolate).undefined_value();
}
// Part of
// https://tc39.es/proposal-explicit-resource-management/#sec-getdisposemethod
BUILTIN(AsyncDisposeFromSyncDispose) {
HandleScope scope(isolate);
// 1. If hint is async-dispose
// b. If GetMethod(V, @@asyncDispose) is undefined,
// i. If GetMethod(V, @@dispose) is not undefined, then
// 1. Let closure be a new Abstract Closure with no parameters that
// captures method and performs the following steps when called:
// a. Let O be the this value.
// b. Let promiseCapability be ! NewPromiseCapability(%Promise%).
DirectHandle<Object> receiver = args.receiver();
DirectHandle<JSPromise> promise = isolate->factory()->NewJSPromise();
// c. Let result be Completion(Call(method, O)).
DirectHandle<JSFunction> sync_method(
Cast<JSFunction>(isolate->context()->GetNoCell(static_cast<int>(
JSDisposableStackBase::AsyncDisposeFromSyncDisposeContextSlots::
kMethod))),
isolate);
MaybeDirectHandle<Object> result =
Execution::Call(isolate, sync_method, receiver, {});
if (!result.is_null()) {
// e. Perform ? Call(promiseCapability.[[Resolve]], undefined, «
// undefined »).
JSPromise::Resolve(promise, isolate->factory()->undefined_value())
.ToHandleChecked();
} else {
Tagged<Object> exception = isolate->exception();
if (!isolate->is_catchable_by_javascript(exception)) {
return {};
}
// d. IfAbruptRejectPromise(result, promiseCapability).
isolate->clear_internal_exception();
isolate->clear_pending_message();
JSPromise::Reject(promise, direct_handle(exception, isolate));
}
// f. Return promiseCapability.[[Promise]].
return *promise;
}
// https://tc39.es/proposal-explicit-resource-management/#sec-asyncdisposablestack
BUILTIN(AsyncDisposableStackConstructor) {
const char kMethodName[] = "AsyncDisposableStack";
HandleScope scope(isolate);
isolate->CountUsage(v8::Isolate::kExplicitResourceManagement);
// 1. If NewTarget is undefined, throw a TypeError exception.
if (!IsJSReceiver(*args.new_target(), isolate)) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kConstructorNotFunction,
isolate->factory()->NewStringFromAsciiChecked(
kMethodName)));
}
// 2. Let asyncDisposableStack be ? OrdinaryCreateFromConstructor(NewTarget,
// "%AsyncDisposableStack.prototype%", « [[AsyncDisposableState]],
// [[DisposeCapability]] »).
DirectHandle<Map> map;
DirectHandle<JSFunction> target = args.target();
DirectHandle<JSReceiver> new_target = Cast<JSReceiver>(args.new_target());
DCHECK_EQ(*target,
target->native_context()->js_async_disposable_stack_function());
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, map, JSFunction::GetDerivedMap(isolate, target, new_target));
DirectHandle<JSAsyncDisposableStack> async_disposable_stack =
isolate->factory()->NewJSAsyncDisposableStack(map);
// 3. Set asyncDisposableStack.[[AsyncDisposableState]] to pending.
// 4. Set asyncDisposableStack.[[DisposeCapability]] to
// NewDisposeCapability().
JSDisposableStackBase::InitializeJSDisposableStackBase(
isolate, async_disposable_stack);
// 5. Return asyncDisposableStack.
return *async_disposable_stack;
}
// https://tc39.es/proposal-explicit-resource-management/#sec-asyncdisposablestack.prototype.use
BUILTIN(AsyncDisposableStackPrototypeUse) {
const char kMethodName[] = "AsyncDisposableStack.prototype.use";
HandleScope scope(isolate);
// 1. Let asyncDisposableStack be the this value.
// 2. Perform ? RequireInternalSlot(asyncDisposableStack,
// [[AsyncDisposableState]]).
CHECK_RECEIVER(JSAsyncDisposableStack, async_disposable_stack, kMethodName);
DirectHandle<JSAny> value = args.at<JSAny>(1);
// 3. If asyncDisposableStack.[[AsyncDisposableState]] is disposed, throw a
// ReferenceError exception.
if (async_disposable_stack->state() == DisposableStackState::kDisposed) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate,
NewReferenceError(
MessageTemplate::kDisposableStackIsDisposed,
isolate->factory()->NewStringFromAsciiChecked(kMethodName)));
}
// 4. Perform ?
// AddDisposableResource(asyncDisposableStack.[[DisposeCapability]],
// value, async-dispose).
DirectHandle<Object> method;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, method,
JSDisposableStackBase::CheckValueAndGetDisposeMethod(
isolate, value, DisposeMethodHint::kAsyncDispose));
JSDisposableStackBase::Add(
isolate, async_disposable_stack,
(IsNullOrUndefined(*value) ? isolate->factory()->undefined_value()
: value),
method, DisposeMethodCallType::kValueIsReceiver,
DisposeMethodHint::kAsyncDispose);
// 5. Return value.
return *value;
}
// https://tc39.es/proposal-explicit-resource-management/#sec-asyncdisposablestack.prototype.disposeAsync
BUILTIN(AsyncDisposableStackPrototypeDisposeAsync) {
HandleScope scope(isolate);
// 1. Let asyncDisposableStack be the this value.
DirectHandle<Object> receiver = args.receiver();
// 2. Let promiseCapability be ! NewPromiseCapability(%Promise%).
DirectHandle<JSPromise> promise = isolate->factory()->NewJSPromise();
// 3. If asyncDisposableStack does not have an [[AsyncDisposableState]]
// internal slot, then
if (!IsJSAsyncDisposableStack(*receiver)) {
// a. Perform ! Call(promiseCapability.[[Reject]], undefined, « a newly
// created TypeError object »).
JSPromise::Reject(promise,
isolate->factory()->NewTypeError(
MessageTemplate::kNotAnAsyncDisposableStack));
// b. Return promiseCapability.[[Promise]].
return *promise;
}
DirectHandle<JSAsyncDisposableStack> async_disposable_stack =
Cast<JSAsyncDisposableStack>(receiver);
// 4. If asyncDisposableStack.[[AsyncDisposableState]] is disposed, then
if (async_disposable_stack->state() == DisposableStackState::kDisposed) {
// a. Perform ! Call(promiseCapability.[[Resolve]], undefined, «
// undefined »).
JSPromise::Resolve(
promise,
direct_handle(ReadOnlyRoots(isolate).undefined_value(), isolate))
.ToHandleChecked();
// b. Return promiseCapability.[[Promise]].
return *promise;
}
// 5. Set asyncDisposableStack.[[AsyncDisposableState]] to disposed.
async_disposable_stack->set_state(DisposableStackState::kDisposed);
// 6. Let result be
// DisposeResources(asyncDisposableStack.[[DisposeCapability]],
// NormalCompletion(undefined)).
// 7. IfAbruptRejectPromise(result, promiseCapability).
// 8. Perform ! Call(promiseCapability.[[Resolve]], undefined, « result
// »).
// 9. Return promiseCapability.[[Promise]].
MAYBE_RETURN(JSAsyncDisposableStack::NextDisposeAsyncIteration(
isolate, async_disposable_stack, promise),
ReadOnlyRoots(isolate).exception());
return *promise;
}
// https://tc39.es/proposal-explicit-resource-management/#sec-get-asyncdisposablestack.prototype.disposed
BUILTIN(AsyncDisposableStackPrototypeGetDisposed) {
const char kMethodName[] = "get AsyncDisposableStack.prototype.disposed";
HandleScope scope(isolate);
// 1. Let AsyncdisposableStack be the this value.
// 2. Perform ? RequireInternalSlot(asyncDisposableStack,
// [[AsyncDisposableState]]).
CHECK_RECEIVER(JSAsyncDisposableStack, async_disposable_stack, kMethodName);
// 3. If AsyncdisposableStack.[[AsyncDisposableState]] is disposed, return
// true.
// 4. Otherwise, return false.
return *(isolate->factory()->ToBoolean(async_disposable_stack->state() ==
DisposableStackState::kDisposed));
}
// https://tc39.es/proposal-explicit-resource-management/#sec-asyncdisposablestack.prototype.adopt
BUILTIN(AsyncDisposableStackPrototypeAdopt) {
const char kMethodName[] = "AsyncDisposableStack.prototype.adopt";
HandleScope scope(isolate);
DirectHandle<Object> value = args.at(1);
DirectHandle<Object> on_dispose_async = args.at(2);
// 1. Let asyncDisposableStack be the this value.
// 2. Perform ? RequireInternalSlot(asyncDisposableStack,
// [[AsyncDisposableState]]).
CHECK_RECEIVER(JSAsyncDisposableStack, async_disposable_stack, kMethodName);
// 3. If asyncDisposableStack.[[AsyncDisposableState]] is disposed, throw a
// ReferenceError exception.
if (async_disposable_stack->state() == DisposableStackState::kDisposed) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate,
NewReferenceError(
MessageTemplate::kDisposableStackIsDisposed,
isolate->factory()->NewStringFromAsciiChecked(kMethodName)));
}
// 4. If IsCallable(onDisposeAsync) is false, throw a TypeError exception.
if (!IsCallable(*on_dispose_async)) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kNotCallable, on_dispose_async));
}
// 5. Let closure be a new Abstract Closure with no parameters that captures
// value and onDisposeAsync and performs the following steps when called:
// a. Return ? Call(onDisposeAsync, undefined, « value »).
// 6. Let F be CreateBuiltinFunction(closure, 0, "", « »).
// 7. Perform ?
// AddDisposableResource(asyncDisposableStack.[[DisposeCapability]],
// undefined, async-dispose, F).
// Instead of creating an abstract closure and a function, we pass
// DisposeMethodCallType::kArgument so at the time of disposal, the value will
// be passed as the argument to the method.
JSDisposableStackBase::Add(isolate, async_disposable_stack, value,
on_dispose_async,
DisposeMethodCallType::kValueIsArgument,
DisposeMethodHint::kAsyncDispose);
// 8. Return value.
return *value;
}
// https://tc39.es/proposal-explicit-resource-management/#sec-asyncdisposablestack.prototype.defer
BUILTIN(AsyncDisposableStackPrototypeDefer) {
const char kMethodName[] = "AsyncDisposableStack.prototype.defer";
HandleScope scope(isolate);
DirectHandle<Object> on_dispose_async = args.at(1);
// 1. Let asyncDisposableStack be the this value.
// 2. Perform ? RequireInternalSlot(asyncDisposableStack,
// [[AsyncDisposableState]]).
CHECK_RECEIVER(JSAsyncDisposableStack, async_disposable_stack, kMethodName);
// 3. If asyncDisposableStack.[[AsyncDisposableState]] is disposed, throw a
// ReferenceError exception.
if (async_disposable_stack->state() == DisposableStackState::kDisposed) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate,
NewReferenceError(
MessageTemplate::kDisposableStackIsDisposed,
isolate->factory()->NewStringFromAsciiChecked(kMethodName)));
}
// 4. If IsCallable(onDisposeAsync) is false, throw a TypeError exception.
if (!IsCallable(*on_dispose_async)) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kNotCallable, on_dispose_async));
}
// 5. Perform ?
// AddDisposableResource(asyncDisposableStack.[[DisposeCapability]],
// undefined, async-dispose, onDisposeAsync).
JSDisposableStackBase::Add(
isolate, async_disposable_stack, isolate->factory()->undefined_value(),
on_dispose_async, DisposeMethodCallType::kValueIsReceiver,
DisposeMethodHint::kAsyncDispose);
// 6. Return undefined.
return ReadOnlyRoots(isolate).undefined_value();
}
// https://tc39.es/proposal-explicit-resource-management/#sec-asyncdisposablestack.prototype.move
BUILTIN(AsyncDisposableStackPrototypeMove) {
const char kMethodName[] = "AsyncDisposableStack.prototype.move";
HandleScope scope(isolate);
// 1. Let asyncDisposableStack be the this value.
// 2. Perform ? RequireInternalSlot(asyncDisposableStack,
// [[AsyncDisposableState]]).
CHECK_RECEIVER(JSAsyncDisposableStack, async_disposable_stack, kMethodName);
// 3. If asyncDisposableStack.[[AsyncDisposableState]] is disposed, throw a
// ReferenceError exception.
if (async_disposable_stack->state() == DisposableStackState::kDisposed) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate,
NewReferenceError(
MessageTemplate::kDisposableStackIsDisposed,
isolate->factory()->NewStringFromAsciiChecked(kMethodName)));
}
// 4. Let newAsyncDisposableStack be ?
// OrdinaryCreateFromConstructor(%AsyncDisposableStack%,
// "%AsyncDisposableStack.prototype%", « [[AsyncDisposableState]],
// [[DisposeCapability]] »).
// 5. Set newAsyncDisposableStack.[[AsyncDisposableState]] to pending.
Tagged<JSFunction> constructor_function =
Cast<JSFunction>(isolate->native_context()->GetNoCell(
Context::JS_ASYNC_DISPOSABLE_STACK_FUNCTION_INDEX));
DirectHandle<Map> map(constructor_function->initial_map(), isolate);
DirectHandle<JSAsyncDisposableStack> new_async_disposable_stack =
isolate->factory()->NewJSAsyncDisposableStack(map);
// 6. Set newAsyncDisposableStack.[[DisposeCapability]] to
// asyncDisposableStack.[[DisposeCapability]].
new_async_disposable_stack->set_stack(async_disposable_stack->stack());
new_async_disposable_stack->set_length(async_disposable_stack->length());
new_async_disposable_stack->set_state(DisposableStackState::kPending);
new_async_disposable_stack->set_error(
*(isolate->factory()->uninitialized_value()));
// 7. Set asyncDisposableStack.[[DisposeCapability]] to
// NewDisposeCapability().
async_disposable_stack->set_stack(ReadOnlyRoots(isolate).empty_fixed_array());
async_disposable_stack->set_length(0);
async_disposable_stack->set_error(
*(isolate->factory()->uninitialized_value()));
// 8. Set disposableStack.[[DisposableState]] to disposed.
async_disposable_stack->set_state(DisposableStackState::kDisposed);
// 9. Return newDisposableStack.
return *new_async_disposable_stack;
}
} // namespace internal
} // namespace v8