blob: f83a2fd9ec7a983f293be678b6006a8df1341411 [file] [log] [blame]
/*
* Copyright (C) 2013-2017 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "JSMicrotask.h"
#include "AggregateError.h"
#include "CatchScope.h"
#include "Debugger.h"
#include "DeferTermination.h"
#include "GlobalObjectMethodTable.h"
#include "JSGenerator.h"
#include "JSGlobalObject.h"
#include "JSObjectInlines.h"
#include "JSPromise.h"
#include "JSPromiseCombinatorsContext.h"
#include "JSPromiseCombinatorsGlobalContext.h"
#include "JSPromiseConstructor.h"
#include "JSPromisePrototype.h"
#include "JSPromiseReaction.h"
#include "Microtask.h"
#include "ObjectConstructor.h"
WTF_ALLOW_UNSAFE_BUFFER_USAGE_BEGIN
namespace JSC {
static ALWAYS_INLINE JSCell* dynamicCastToCell(JSValue value)
{
if (value.isCell())
return value.asCell();
return nullptr;
}
static void promiseResolveThenableJobFastSlow(JSGlobalObject* globalObject, JSPromise* promise, JSPromise* promiseToResolve)
{
VM& vm = globalObject->vm();
auto scope = DECLARE_CATCH_SCOPE(vm);
JSObject* constructor = promiseSpeciesConstructor(globalObject, promise);
if (scope.exception()) [[unlikely]]
return;
auto [resolve, reject] = promiseToResolve->createResolvingFunctions(vm, globalObject);
auto capability = JSPromise::createNewPromiseCapability(globalObject, constructor);
if (!scope.exception()) [[likely]] {
promise->performPromiseThen(vm, globalObject, resolve, reject, capability, jsUndefined());
return;
}
JSValue error = scope.exception()->value();
if (!scope.clearExceptionExceptTermination()) [[unlikely]]
return;
MarkedArgumentBuffer arguments;
arguments.append(error);
ASSERT(!arguments.hasOverflowed());
auto callData = JSC::getCallDataInline(reject);
call(globalObject, reject, callData, jsUndefined(), arguments);
EXCEPTION_ASSERT(scope.exception() || true);
}
static void promiseResolveThenableJobWithoutPromiseFastSlow(JSGlobalObject* globalObject, JSPromise* promise, JSValue onFulfilled, JSValue onRejected, JSValue context)
{
VM& vm = globalObject->vm();
auto scope = DECLARE_CATCH_SCOPE(vm);
JSObject* constructor = promiseSpeciesConstructor(globalObject, promise);
if (scope.exception()) [[unlikely]]
return;
auto [resolve, reject] = JSPromise::createResolvingFunctionsWithoutPromise(vm, globalObject, onFulfilled, onRejected, context);
auto capability = JSPromise::createNewPromiseCapability(globalObject, constructor);
if (!scope.exception()) [[likely]] {
promise->performPromiseThen(vm, globalObject, resolve, reject, capability, jsUndefined());
return;
}
JSValue error = scope.exception()->value();
if (!scope.clearExceptionExceptTermination()) [[unlikely]]
return;
MarkedArgumentBuffer arguments;
arguments.append(error);
ASSERT(!arguments.hasOverflowed());
auto callData = JSC::getCallDataInline(reject);
call(globalObject, reject, callData, jsUndefined(), arguments);
EXCEPTION_ASSERT(scope.exception() || true);
}
static void promiseResolveThenableJobWithInternalMicrotaskFastSlow(JSGlobalObject* globalObject, JSPromise* promise, InternalMicrotask task, JSValue context)
{
VM& vm = globalObject->vm();
auto scope = DECLARE_CATCH_SCOPE(vm);
JSObject* constructor = promiseSpeciesConstructor(globalObject, promise);
if (scope.exception()) [[unlikely]]
return;
auto [resolve, reject] = JSPromise::createResolvingFunctionsWithInternalMicrotask(vm, globalObject, task, context);
auto capability = JSPromise::createNewPromiseCapability(globalObject, constructor);
if (!scope.exception()) [[likely]] {
promise->performPromiseThen(vm, globalObject, resolve, reject, capability, jsUndefined());
return;
}
JSValue error = scope.exception()->value();
if (!scope.clearExceptionExceptTermination()) [[unlikely]]
return;
MarkedArgumentBuffer arguments;
arguments.append(error);
ASSERT(!arguments.hasOverflowed());
auto callData = JSC::getCallDataInline(reject);
call(globalObject, reject, callData, jsUndefined(), arguments);
EXCEPTION_ASSERT(scope.exception() || true);
}
static void promiseResolveThenableJob(JSGlobalObject* globalObject, JSValue promise, JSValue then, JSValue resolve, JSValue reject)
{
VM& vm = globalObject->vm();
auto scope = DECLARE_CATCH_SCOPE(vm);
{
MarkedArgumentBuffer arguments;
arguments.append(resolve);
arguments.append(reject);
ASSERT(!arguments.hasOverflowed());
callMicrotask(globalObject, then, promise, dynamicCastToCell(then), arguments, "|then| is not a function"_s);
if (!scope.exception()) [[likely]]
return;
}
JSValue error = scope.exception()->value();
if (!scope.clearExceptionExceptTermination()) [[unlikely]]
return;
MarkedArgumentBuffer arguments;
arguments.append(error);
ASSERT(!arguments.hasOverflowed());
call(globalObject, reject, jsUndefined(), arguments, "|reject| is not a function"_s);
EXCEPTION_ASSERT(scope.exception() || true);
}
static void promiseRaceResolveJob(JSGlobalObject* globalObject, VM& vm, JSPromise* promise, JSValue resolution, JSPromise::Status status)
{
auto scope = DECLARE_THROW_SCOPE(vm);
if (promise->status() != JSPromise::Status::Pending)
return;
switch (status) {
case JSPromise::Status::Pending: {
RELEASE_ASSERT_NOT_REACHED();
break;
}
case JSPromise::Status::Fulfilled: {
scope.release();
promise->resolve(globalObject, resolution);
break;
}
case JSPromise::Status::Rejected: {
scope.release();
promise->reject(vm, globalObject, resolution);
break;
}
}
}
static void promiseAllResolveJob(JSGlobalObject* globalObject, VM& vm, JSPromise* promise, JSValue resolution, JSPromiseCombinatorsContext* context, JSPromise::Status status)
{
auto scope = DECLARE_THROW_SCOPE(vm);
auto* globalContext = jsCast<JSPromiseCombinatorsGlobalContext*>(context->globalContext());
switch (status) {
case JSPromise::Status::Pending: {
RELEASE_ASSERT_NOT_REACHED();
break;
}
case JSPromise::Status::Fulfilled: {
auto* values = jsCast<JSArray*>(globalContext->values());
uint64_t index = context->index();
values->putDirectIndex(globalObject, index, resolution);
RETURN_IF_EXCEPTION(scope, void());
uint64_t count = globalContext->remainingElementsCount().toIndex(globalObject, "count exceeds size"_s);
RETURN_IF_EXCEPTION(scope, void());
--count;
globalContext->setRemainingElementsCount(vm, jsNumber(count));
if (!count) {
scope.release();
promise->resolve(globalObject, values);
}
break;
}
case JSPromise::Status::Rejected: {
scope.release();
promise->reject(vm, globalObject, resolution);
break;
}
}
}
static void promiseAllSettledResolveJob(JSGlobalObject* globalObject, VM& vm, JSPromise* promise, JSValue resolution, JSPromiseCombinatorsContext* context, JSPromise::Status status)
{
auto scope = DECLARE_THROW_SCOPE(vm);
auto* globalContext = jsCast<JSPromiseCombinatorsGlobalContext*>(context->globalContext());
auto* values = jsCast<JSArray*>(globalContext->values());
uint64_t index = context->index();
JSObject* resultObject = nullptr;
switch (status) {
case JSPromise::Status::Pending: {
RELEASE_ASSERT_NOT_REACHED();
break;
}
case JSPromise::Status::Fulfilled: {
resultObject = createPromiseAllSettledFulfilledResult(globalObject, resolution);
break;
}
case JSPromise::Status::Rejected: {
resultObject = createPromiseAllSettledRejectedResult(globalObject, resolution);
break;
}
}
values->putDirectIndex(globalObject, index, resultObject);
RETURN_IF_EXCEPTION(scope, void());
uint64_t count = globalContext->remainingElementsCount().toIndex(globalObject, "count exceeds size"_s);
RETURN_IF_EXCEPTION(scope, void());
--count;
globalContext->setRemainingElementsCount(vm, jsNumber(count));
if (!count) {
scope.release();
promise->resolve(globalObject, values);
}
}
static void promiseAnyResolveJob(JSGlobalObject* globalObject, VM& vm, JSPromise* promise, JSValue resolution, JSPromiseCombinatorsContext* context, JSPromise::Status status)
{
auto scope = DECLARE_THROW_SCOPE(vm);
auto* globalContext = jsCast<JSPromiseCombinatorsGlobalContext*>(context->globalContext());
switch (status) {
case JSPromise::Status::Pending: {
RELEASE_ASSERT_NOT_REACHED();
break;
}
case JSPromise::Status::Fulfilled: {
scope.release();
promise->resolve(globalObject, resolution);
break;
}
case JSPromise::Status::Rejected: {
auto* errors = jsCast<JSArray*>(globalContext->values());
uint64_t index = context->index();
errors->putDirectIndex(globalObject, index, resolution);
RETURN_IF_EXCEPTION(scope, void());
uint64_t count = globalContext->remainingElementsCount().toIndex(globalObject, "count exceeds size"_s);
RETURN_IF_EXCEPTION(scope, void());
--count;
globalContext->setRemainingElementsCount(vm, jsNumber(count));
if (!count) {
auto* aggregateError = createAggregateError(globalObject, vm, globalObject->errorStructure(ErrorType::AggregateError), errors, jsUndefined(), jsUndefined());
scope.release();
promise->reject(vm, globalObject, aggregateError);
}
break;
}
}
}
void runInternalMicrotask(JSGlobalObject* globalObject, InternalMicrotask task, std::span<const JSValue, maxMicrotaskArguments> arguments)
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
switch (task) {
case InternalMicrotask::PromiseResolveThenableJobFast: {
auto* promise = jsCast<JSPromise*>(arguments[0]);
auto* promiseToResolve = jsCast<JSPromise*>(arguments[1]);
if (!promiseSpeciesWatchpointIsValid(vm, promise)) [[unlikely]]
RELEASE_AND_RETURN(scope, promiseResolveThenableJobFastSlow(globalObject, promise, promiseToResolve));
scope.release();
promise->performPromiseThenWithInternalMicrotask(vm, globalObject, InternalMicrotask::PromiseResolveWithoutHandlerJob, promiseToResolve, jsUndefined());
return;
}
case InternalMicrotask::PromiseResolveThenableJobWithoutPromiseFast: {
auto* promise = jsCast<JSPromise*>(arguments[0]);
JSValue onFulfilled = arguments[1];
JSValue onRejected = arguments[2];
JSValue context = arguments[3];
if (!promiseSpeciesWatchpointIsValid(vm, promise)) [[unlikely]]
RELEASE_AND_RETURN(scope, promiseResolveThenableJobWithoutPromiseFastSlow(globalObject, promise, onFulfilled, onRejected, context));
switch (promise->status()) {
case JSPromise::Status::Pending: {
auto* reaction = JSPromiseReaction::create(vm, jsUndefined(), onFulfilled, onRejected, context, jsDynamicCast<JSPromiseReaction*>(promise->reactionsOrResult()));
promise->setReactionsOrResult(vm, reaction);
break;
}
case JSPromise::Status::Rejected: {
if (!promise->isHandled())
globalObject->globalObjectMethodTable()->promiseRejectionTracker(globalObject, promise, JSPromiseRejectionOperation::Handle);
JSPromise::rejectWithoutPromise(globalObject, promise->reactionsOrResult(), onFulfilled, onRejected, context);
break;
}
case JSPromise::Status::Fulfilled: {
JSPromise::fulfillWithoutPromise(globalObject, promise->reactionsOrResult(), onFulfilled, onRejected, context);
break;
}
}
promise->markAsHandled();
return;
}
case InternalMicrotask::PromiseResolveThenableJobWithInternalMicrotaskFast: {
auto* promise = jsCast<JSPromise*>(arguments[0]);
auto task = static_cast<InternalMicrotask>(arguments[1].asInt32());
JSValue context = arguments[2];
if (!promiseSpeciesWatchpointIsValid(vm, promise)) [[unlikely]]
RELEASE_AND_RETURN(scope, promiseResolveThenableJobWithInternalMicrotaskFastSlow(globalObject, promise, task, context));
switch (promise->status()) {
case JSPromise::Status::Pending: {
JSValue encodedTask = jsNumber(static_cast<int32_t>(task));
auto* reaction = JSPromiseReaction::create(vm, jsUndefined(), encodedTask, encodedTask, context, jsDynamicCast<JSPromiseReaction*>(promise->reactionsOrResult()));
promise->setReactionsOrResult(vm, reaction);
break;
}
case JSPromise::Status::Rejected: {
if (!promise->isHandled())
globalObject->globalObjectMethodTable()->promiseRejectionTracker(globalObject, promise, JSPromiseRejectionOperation::Handle);
JSPromise::rejectWithInternalMicrotask(globalObject, promise->reactionsOrResult(), task, context);
break;
}
case JSPromise::Status::Fulfilled: {
JSPromise::fulfillWithInternalMicrotask(globalObject, promise->reactionsOrResult(), task, context);
break;
}
}
promise->markAsHandled();
return;
}
case InternalMicrotask::PromiseResolveThenableJob: {
JSValue promise = arguments[0];
JSValue then = arguments[1];
JSValue resolve = arguments[2];
JSValue reject = arguments[3];
RELEASE_AND_RETURN(scope, promiseResolveThenableJob(globalObject, promise, then, resolve, reject));
}
case InternalMicrotask::PromiseResolveWithoutHandlerJob: {
auto* promise = jsCast<JSPromise*>(arguments[0]);
JSValue resolution = arguments[1];
switch (static_cast<JSPromise::Status>(arguments[2].asInt32())) {
case JSPromise::Status::Pending: {
RELEASE_ASSERT_NOT_REACHED();
break;
}
case JSPromise::Status::Fulfilled: {
scope.release();
promise->resolvePromise(globalObject, resolution);
break;
}
case JSPromise::Status::Rejected: {
scope.release();
promise->rejectPromise(vm, globalObject, resolution);
break;
}
}
return;
}
case InternalMicrotask::PromiseRaceResolveJob:
RELEASE_AND_RETURN(scope, promiseRaceResolveJob(globalObject, vm, jsCast<JSPromise*>(arguments[0]), arguments[1], static_cast<JSPromise::Status>(arguments[2].asInt32())));
case InternalMicrotask::PromiseAllResolveJob:
RELEASE_AND_RETURN(scope, promiseAllResolveJob(globalObject, vm, jsCast<JSPromise*>(arguments[0]), arguments[1], jsCast<JSPromiseCombinatorsContext*>(arguments[3]), static_cast<JSPromise::Status>(arguments[2].asInt32())));
case InternalMicrotask::PromiseAllSettledResolveJob:
RELEASE_AND_RETURN(scope, promiseAllSettledResolveJob(globalObject, vm, jsCast<JSPromise*>(arguments[0]), arguments[1], jsCast<JSPromiseCombinatorsContext*>(arguments[3]), static_cast<JSPromise::Status>(arguments[2].asInt32())));
case InternalMicrotask::PromiseAnyResolveJob:
RELEASE_AND_RETURN(scope, promiseAnyResolveJob(globalObject, vm, jsCast<JSPromise*>(arguments[0]), arguments[1], jsCast<JSPromiseCombinatorsContext*>(arguments[3]), static_cast<JSPromise::Status>(arguments[2].asInt32())));
case InternalMicrotask::PromiseReactionJob: {
JSValue promiseOrCapability = arguments[0];
JSValue handler = arguments[1];
JSValue context = arguments[3];
ASSERT(!promiseOrCapability.isUndefinedOrNull());
JSValue result;
JSValue error;
{
auto catchScope = DECLARE_CATCH_SCOPE(vm);
if (context.isUndefinedOrNull())
result = callMicrotask(globalObject, handler, jsUndefined(), dynamicCastToCell(handler), ArgList { std::bit_cast<EncodedJSValue*>(arguments.data() + 2), 1 }, "handler is not a function"_s);
else
result = callMicrotask(globalObject, handler, jsUndefined(), dynamicCastToCell(context), ArgList { std::bit_cast<EncodedJSValue*>(arguments.data() + 2), 2 }, "handler is not a function"_s);
if (catchScope.exception()) {
error = catchScope.exception()->value();
if (!catchScope.clearExceptionExceptTermination()) [[unlikely]] {
scope.release();
return;
}
}
}
if (error) {
if (auto* promise = jsDynamicCast<JSPromise*>(promiseOrCapability))
RELEASE_AND_RETURN(scope, promise->rejectPromise(vm, globalObject, error));
JSValue reject = promiseOrCapability.get(globalObject, vm.propertyNames->reject);
RETURN_IF_EXCEPTION(scope, void());
MarkedArgumentBuffer arguments;
arguments.append(error);
ASSERT(!arguments.hasOverflowed());
scope.release();
call(globalObject, reject, jsUndefined(), arguments, "reject is not a function"_s);
return;
}
if (auto* promise = jsDynamicCast<JSPromise*>(promiseOrCapability))
RELEASE_AND_RETURN(scope, promise->resolvePromise(globalObject, result));
JSValue resolve = promiseOrCapability.get(globalObject, vm.propertyNames->resolve);
RETURN_IF_EXCEPTION(scope, void());
MarkedArgumentBuffer arguments;
arguments.append(result);
ASSERT(!arguments.hasOverflowed());
scope.release();
call(globalObject, resolve, jsUndefined(), arguments, "resolve is not a function"_s);
return;
}
case InternalMicrotask::PromiseReactionJobWithoutPromise: {
JSValue handler = arguments[0];
JSValue context = arguments[2];
if (context.isUndefinedOrNull()) {
scope.release();
callMicrotask(globalObject, handler, jsUndefined(), dynamicCastToCell(handler), ArgList { std::bit_cast<EncodedJSValue*>(arguments.data() + 1), 1 }, "handler is not a function"_s);
} else {
scope.release();
callMicrotask(globalObject, handler, jsUndefined(), dynamicCastToCell(context), ArgList { std::bit_cast<EncodedJSValue*>(arguments.data() + 1), 2 }, "handler is not a function"_s);
}
return;
}
case InternalMicrotask::InvokeFunctionJob: {
JSValue handler = arguments[0];
scope.release();
callMicrotask(globalObject, handler, jsUndefined(), nullptr, ArgList { }, "handler is not a function"_s);
return;
}
case InternalMicrotask::AsyncFunctionResume: {
JSValue resolution = arguments[1];
auto* generator = jsCast<JSGenerator*>(arguments[3]);
JSGenerator::ResumeMode resumeMode = JSGenerator::ResumeMode::NormalMode;
switch (static_cast<JSPromise::Status>(arguments[2].asInt32())) {
case JSPromise::Status::Pending: {
RELEASE_ASSERT_NOT_REACHED();
break;
}
case JSPromise::Status::Rejected: {
resumeMode = JSGenerator::ResumeMode::ThrowMode;
break;
}
case JSPromise::Status::Fulfilled: {
resumeMode = JSGenerator::ResumeMode::NormalMode;
break;
}
}
int32_t state = generator->state();
generator->setState(static_cast<int32_t>(JSGenerator::State::Executing));
JSValue next = generator->next();
JSValue thisValue = generator->thisValue();
JSValue frame = generator->frame();
std::array<EncodedJSValue, 5> args = { {
JSValue::encode(generator),
JSValue::encode(jsNumber(state)),
JSValue::encode(resolution),
JSValue::encode(jsNumber(static_cast<int32_t>(resumeMode))),
JSValue::encode(frame),
} };
JSValue value;
JSValue error;
{
auto catchScope = DECLARE_CATCH_SCOPE(vm);
value = callMicrotask(globalObject, next, thisValue, generator, ArgList { args.data(), args.size() }, "handler is not a function"_s);
if (catchScope.exception()) {
error = catchScope.exception()->value();
if (!catchScope.clearExceptionExceptTermination()) [[unlikely]] {
scope.release();
return;
}
}
}
if (error) {
auto* promise = jsCast<JSPromise*>(generator->context());
scope.release();
promise->reject(vm, globalObject, error);
return;
}
if (generator->state() == static_cast<int32_t>(JSGenerator::State::Executing)) {
auto* promise = jsCast<JSPromise*>(generator->context());
scope.release();
promise->resolve(globalObject, value);
return;
}
scope.release();
JSPromise::resolveWithInternalMicrotaskForAsyncAwait(globalObject, value, InternalMicrotask::AsyncFunctionResume, generator);
return;
}
case InternalMicrotask::Opaque: {
RELEASE_ASSERT_NOT_REACHED();
return;
}
}
}
} // namespace JSC
WTF_ALLOW_UNSAFE_BUFFER_USAGE_END