current commit
251004.9 · deps: update V8 to 14.1.146.11
// 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/objects/js-disposable-stack.h"

#include "include/v8-maybe.h"
#include "include/v8-promise.h"
#include "src/base/logging.h"
#include "src/base/macros.h"
#include "src/debug/debug.h"
#include "src/execution/isolate.h"
#include "src/handles/handles.h"
#include "src/handles/maybe-handles.h"
#include "src/heap/factory.h"
#include "src/objects/fixed-array-inl.h"
#include "src/objects/heap-object.h"
#include "src/objects/js-disposable-stack-inl.h"
#include "src/objects/js-function.h"
#include "src/objects/js-objects.h"
#include "src/objects/js-promise-inl.h"
#include "src/objects/js-promise.h"
#include "src/objects/objects-inl.h"
#include "src/objects/objects.h"
#include "src/objects/oddball.h"
#include "src/objects/tagged.h"

namespace v8 {
namespace internal {

#define CHECK_EXCEPTION_ON_DISPOSAL(isolate, disposable_stack, return_value) \
  do {                                                                       \
    DCHECK(isolate->has_exception());                                        \
    DirectHandle<Object> current_error(isolate->exception(), isolate);       \
    DirectHandle<Object> current_error_message(isolate->pending_message(),   \
                                               isolate);                     \
    if (!isolate->is_catchable_by_javascript(*current_error)) {              \
      return return_value;                                                   \
    }                                                                        \
    isolate->clear_internal_exception();                                     \
    isolate->clear_pending_message();                                        \
    HandleErrorInDisposal(isolate, disposable_stack, current_error,          \
                          current_error_message);                            \
  } while (false)

// https://arai-a.github.io/ecma262-compare/?pr=3000&id=sec-disposeresources
MaybeDirectHandle<Object> JSDisposableStackBase::DisposeResources(
    Isolate* isolate, DirectHandle<JSDisposableStackBase> disposable_stack,
    DisposableStackResourcesType resources_type) {
  DCHECK(!IsUndefined(disposable_stack->stack()));
  DCHECK_EQ(disposable_stack->state(), DisposableStackState::kDisposed);

  DirectHandle<FixedArray> stack(disposable_stack->stack(), isolate);

  // 1. Let needsAwait be false.
  // 2. Let hasAwaited be false.
  // `false` is assigned to both in the initialization of the DisposableStack.

  int length = disposable_stack->length();

  MaybeDirectHandle<Object> result;

  // 3. For each element resource of
  // disposeCapability.[[DisposableResourceStack]], in reverse list order, do
  while (length > 0) {
    //  a. Let value be resource.[[ResourceValue]].
    //  b. Let hint be resource.[[Hint]].
    //  c. Let method be resource.[[DisposeMethod]].
    Tagged<Object> stack_type = stack->get(--length);

    Tagged<Object> tagged_method = stack->get(--length);
    DirectHandle<Object> method(tagged_method, isolate);

    Tagged<Object> tagged_value = stack->get(--length);
    DirectHandle<Object> value(tagged_value, isolate);

    DirectHandle<Object> args[] = {value};

    auto stack_type_case = static_cast<int>(Cast<Smi>(stack_type).value());
    DisposeMethodCallType call_type =
        DisposeCallTypeBit::decode(stack_type_case);
    DisposeMethodHint hint = DisposeHintBit::decode(stack_type_case);

    //  d. If hint is sync-dispose and needsAwait is true and hasAwaited is
    //  false, then
    //    i. Perform ! Await(undefined).
    //    ii. Set needsAwait to false.

    if (hint == DisposeMethodHint::kSyncDispose &&
        disposable_stack->needs_await() == true &&
        disposable_stack->has_awaited() == false) {
      //  i. Perform ! Await(undefined).
      //  ii. Set needsAwait to false.
      disposable_stack->set_needs_await(false);

      return ResolveAPromiseWithValueAndReturnIt(
          isolate, isolate->factory()->undefined_value());
    }

    //  e. If method is not undefined, then
    if (!IsUndefined(*method)) {
      //    i. Let result be Completion(Call(method, value)).

      if (call_type == DisposeMethodCallType::kValueIsReceiver) {
        result = Execution::Call(isolate, method, value, {});
      } else if (call_type == DisposeMethodCallType::kValueIsArgument) {
        result = Execution::Call(isolate, method,
                                 isolate->factory()->undefined_value(),
                                 base::VectorOf(args));
      }

      DirectHandle<Object> result_handle;
      //    ii. If result is a normal completion and hint is async-dispose, then
      //      1. Set result to Completion(Await(result.[[Value]])).
      //      2. Set hasAwaited to true.
      if (result.ToHandle(&result_handle)) {
        if (hint == DisposeMethodHint::kAsyncDispose) {
          DCHECK_NE(resources_type, DisposableStackResourcesType::kAllSync);
          disposable_stack->set_length(length);

          disposable_stack->set_has_awaited(true);

          MaybeDirectHandle<JSReceiver> resolved_promise =
              ResolveAPromiseWithValueAndReturnIt(isolate, result_handle);

          if (resolved_promise.is_null()) {
            //    iii. If result is a throw completion, then
            //      1. If completion is a throw completion, then
            //        a. Set result to result.[[Value]].
            //        b. Let suppressed be completion.[[Value]].
            //        c. Let error be a newly created SuppressedError object.
            //        d. Perform CreateNonEnumerableDataPropertyOrThrow(error,
            //        "error", result).
            //        e. Perform CreateNonEnumerableDataPropertyOrThrow(error,
            //         "suppressed", suppressed).
            //        f. Set completion to ThrowCompletion(error).
            //      2. Else,
            //        a. Set completion to result.
            CHECK_EXCEPTION_ON_DISPOSAL(isolate, disposable_stack, {});
          } else {
            return resolved_promise;
          }
        }
      } else {
        CHECK_EXCEPTION_ON_DISPOSAL(isolate, disposable_stack, {});
      }
    } else {
      //  Else,
      //    i. Assert: hint is async-dispose.
      DCHECK_EQ(hint, DisposeMethodHint::kAsyncDispose);
      //    ii. Set needsAwait to true.
      //    iii. NOTE: This can only indicate a case where either null or
      //    undefined was the initialized value of an await using declaration.
      disposable_stack->set_length(length);
      disposable_stack->set_needs_await(true);
    }
  }

  // 4. If needsAwait is true and hasAwaited is false, then
  //   a. Perform ! Await(undefined).
  if (disposable_stack->needs_await() == true &&
      disposable_stack->has_awaited() == false) {
    disposable_stack->set_length(length);
    disposable_stack->set_has_awaited(true);

    return ResolveAPromiseWithValueAndReturnIt(
        isolate, isolate->factory()->undefined_value());
  }

  // 5. NOTE: After disposeCapability has been disposed, it will never be used
  // again. The contents of disposeCapability.[[DisposableResourceStack]] can be
  // discarded in implementations, such as by garbage collection, at this point.
  // 6. Set disposeCapability.[[DisposableResourceStack]] to a new empty List.
  disposable_stack->set_stack(ReadOnlyRoots(isolate).empty_fixed_array());
  disposable_stack->set_length(0);

  Handle<Object> existing_error_handle(disposable_stack->error(), isolate);
  DirectHandle<Object> existing_error_message_handle(
      disposable_stack->error_message(), isolate);
  disposable_stack->set_error(*(isolate->factory()->uninitialized_value()));
  disposable_stack->set_error_message(
      *(isolate->factory()->uninitialized_value()));

  // 7. Return ? completion.
  if (!IsUninitializedHole(*existing_error_handle)) {
    if (disposable_stack->suppressed_error_created() == true) {
      // Created SuppressedError is intentionally suppressed here for debug.
      SuppressDebug while_processing(isolate->debug());
      isolate->Throw(*existing_error_handle);
    } else {
      isolate->ReThrow(*existing_error_handle, *existing_error_message_handle);
    }
    return MaybeDirectHandle<Object>();
  }
  return isolate->factory()->true_value();
}

MaybeDirectHandle<JSReceiver>
JSDisposableStackBase::ResolveAPromiseWithValueAndReturnIt(
    Isolate* isolate, DirectHandle<Object> value) {
  DirectHandle<JSFunction> promise_function = isolate->promise_function();
  DirectHandle<Object> args[] = {value};
  DirectHandle<Object> result;
  ASSIGN_RETURN_ON_EXCEPTION(
      isolate, result,
      Execution::CallBuiltin(isolate, isolate->promise_resolve(),
                             promise_function, base::VectorOf(args)));
  return Cast<JSReceiver>(result);
}

Maybe<bool> JSAsyncDisposableStack::NextDisposeAsyncIteration(
    Isolate* isolate,
    DirectHandle<JSDisposableStackBase> async_disposable_stack,
    DirectHandle<JSPromise> outer_promise) {
  MaybeDirectHandle<Object> result;

  bool done;
  do {
    done = true;

    // 6. Let result be
    //   DisposeResources(asyncDisposableStack.[[DisposeCapability]],
    //   NormalCompletion(undefined)).
    result = DisposeResources(isolate, async_disposable_stack,
                              DisposableStackResourcesType::kAtLeastOneAsync);

    DirectHandle<Object> result_handle;

    if (result.ToHandle(&result_handle)) {
      if (!IsTrue(*result_handle)) {
        DirectHandle<Context> async_disposable_stack_context =
            isolate->factory()->NewBuiltinContext(
                isolate->native_context(),
                static_cast<int>(
                    JSDisposableStackBase::AsyncDisposableStackContextSlots::
                        kLength));
        async_disposable_stack_context->SetNoCell(
            static_cast<int>(JSDisposableStackBase::
                                 AsyncDisposableStackContextSlots::kStack),
            *async_disposable_stack);
        async_disposable_stack_context->SetNoCell(
            static_cast<int>(
                JSDisposableStackBase::AsyncDisposableStackContextSlots::
                    kOuterPromise),
            *outer_promise);

        DirectHandle<JSFunction> on_fulfilled =
            Factory::JSFunctionBuilder{
                isolate,
                isolate->factory()
                    ->async_disposable_stack_on_fulfilled_shared_fun(),
                async_disposable_stack_context}
                .Build();

        DirectHandle<JSFunction> on_rejected =
            Factory::JSFunctionBuilder{
                isolate,
                isolate->factory()
                    ->async_disposable_stack_on_rejected_shared_fun(),
                async_disposable_stack_context}
                .Build();

        DirectHandle<Object> args[] = {on_fulfilled, on_rejected};
        if (Execution::CallBuiltin(isolate, isolate->perform_promise_then(),
                                   Cast<JSPromise>(result_handle),
                                   base::VectorOf(args))
                .is_null()) {
          CHECK_EXCEPTION_ON_DISPOSAL(isolate, async_disposable_stack,
                                      Nothing<bool>());
          done = false;
        }
      } else {
        // 8. Perform ! Call(promiseCapability.[[Resolve]], undefined, « result
        // »).
        if (JSPromise::Resolve(outer_promise,
                               isolate->factory()->undefined_value())
                .is_null()) {
          CHECK_EXCEPTION_ON_DISPOSAL(isolate, async_disposable_stack,
                                      Nothing<bool>());
          done = false;
        }
      }
    } else {
      // 7. IfAbruptRejectPromise(result, promiseCapability).
      DirectHandle<Object> exception(isolate->exception(), isolate);
      if (!isolate->is_catchable_by_javascript(*exception)) {
        return Nothing<bool>();
      }
      isolate->clear_internal_exception();
      isolate->clear_pending_message();
      JSPromise::Reject(outer_promise, exception);
    }
  } while (!done);

  return Just(true);
}

}  // namespace internal
}  // namespace v8