blob: 67e5e38687d76c7dca96600a8b5fa53d56f9488b [file] [log] [blame]
// Copyright 2019 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/builtins/builtins-promise.h'
#include 'src/builtins/builtins-promise-gen.h'
namespace runtime {
extern transitioning runtime
AllowDynamicFunction(implicit context: Context)(JSAny): JSAny;
}
// Unsafe functions that should be used very carefully.
namespace promise_internal {
extern macro PromiseBuiltinsAssembler::ZeroOutEmbedderOffsets(JSPromise): void;
extern macro PromiseBuiltinsAssembler::AllocateJSPromise(Context): HeapObject;
}
namespace promise {
extern macro IsFunctionWithPrototypeSlotMap(Map): bool;
@export
macro PromiseHasHandler(promise: JSPromise): bool {
return promise.HasHandler();
}
@export
macro PromiseInit(promise: JSPromise): void {
promise.reactions_or_result = kZero;
promise.flags = SmiTag(JSPromiseFlags{
status: PromiseState::kPending,
has_handler: false,
handled_hint: false,
async_task_id: 0
});
promise_internal::ZeroOutEmbedderOffsets(promise);
}
macro InnerNewJSPromise(implicit context: Context)(): JSPromise {
const promiseFun = *NativeContextSlot(ContextSlot::PROMISE_FUNCTION_INDEX);
assert(IsFunctionWithPrototypeSlotMap(promiseFun.map));
const promiseMap = UnsafeCast<Map>(promiseFun.prototype_or_initial_map);
const promiseHeapObject = promise_internal::AllocateJSPromise(context);
*UnsafeConstCast(&promiseHeapObject.map) = promiseMap;
const promise = UnsafeCast<JSPromise>(promiseHeapObject);
promise.properties_or_hash = kEmptyFixedArray;
promise.elements = kEmptyFixedArray;
promise.reactions_or_result = kZero;
promise.flags = SmiTag(JSPromiseFlags{
status: PromiseState::kPending,
has_handler: false,
handled_hint: false,
async_task_id: 0
});
return promise;
}
macro NewPromiseFulfillReactionJobTask(implicit context: Context)(
handlerContext: Context, argument: Object, handler: Callable|Undefined,
promiseOrCapability: JSPromise|PromiseCapability|
Undefined): PromiseFulfillReactionJobTask {
const nativeContext = LoadNativeContext(handlerContext);
return new PromiseFulfillReactionJobTask{
map: PromiseFulfillReactionJobTaskMapConstant(),
argument,
context: handlerContext,
handler,
promise_or_capability: promiseOrCapability,
continuation_preserved_embedder_data:
*ContextSlot(
nativeContext, ContextSlot::CONTINUATION_PRESERVED_EMBEDDER_DATA_INDEX)
};
}
macro NewPromiseRejectReactionJobTask(implicit context: Context)(
handlerContext: Context, argument: Object, handler: Callable|Undefined,
promiseOrCapability: JSPromise|PromiseCapability|
Undefined): PromiseRejectReactionJobTask {
const nativeContext = LoadNativeContext(handlerContext);
return new PromiseRejectReactionJobTask{
map: PromiseRejectReactionJobTaskMapConstant(),
argument,
context: handlerContext,
handler,
promise_or_capability: promiseOrCapability,
continuation_preserved_embedder_data:
*ContextSlot(
nativeContext, ContextSlot::CONTINUATION_PRESERVED_EMBEDDER_DATA_INDEX)
};
}
// These allocate and initialize a promise with pending state and
// undefined fields.
//
// This uses the given parent as the parent promise for the promise
// init hook.
@export
transitioning macro NewJSPromise(implicit context: Context)(parent: Object):
JSPromise {
const instance = InnerNewJSPromise();
PromiseInit(instance);
if (IsPromiseHookEnabledOrHasAsyncEventDelegate()) {
runtime::PromiseHookInit(instance, parent);
}
return instance;
}
// This uses undefined as the parent promise for the promise init
// hook.
@export
transitioning macro NewJSPromise(implicit context: Context)(): JSPromise {
return NewJSPromise(Undefined);
}
// This allocates and initializes a promise with the given state and
// fields.
@export
transitioning macro NewJSPromise(implicit context: Context)(
status: constexpr PromiseState, result: JSAny): JSPromise {
assert(status != PromiseState::kPending);
const instance = InnerNewJSPromise();
instance.reactions_or_result = result;
instance.SetStatus(status);
promise_internal::ZeroOutEmbedderOffsets(instance);
if (IsPromiseHookEnabledOrHasAsyncEventDelegate()) {
runtime::PromiseHookInit(instance, Undefined);
}
return instance;
}
macro NewPromiseReaction(implicit context: Context)(
handlerContext: Context, next: Zero|PromiseReaction,
promiseOrCapability: JSPromise|PromiseCapability|Undefined,
fulfillHandler: Callable|Undefined,
rejectHandler: Callable|Undefined): PromiseReaction {
const nativeContext = LoadNativeContext(handlerContext);
return new PromiseReaction{
map: PromiseReactionMapConstant(),
next: next,
reject_handler: rejectHandler,
fulfill_handler: fulfillHandler,
promise_or_capability: promiseOrCapability,
continuation_preserved_embedder_data:
*ContextSlot(
nativeContext, ContextSlot::CONTINUATION_PRESERVED_EMBEDDER_DATA_INDEX)
};
}
extern macro PromiseResolveThenableJobTaskMapConstant(): Map;
// https://tc39.es/ecma262/#sec-newpromiseresolvethenablejob
macro NewPromiseResolveThenableJobTask(implicit context: Context)(
promiseToResolve: JSPromise, thenable: JSReceiver,
then: Callable): PromiseResolveThenableJobTask {
// 2. Let getThenRealmResult be GetFunctionRealm(then).
// 3. If getThenRealmResult is a normal completion, then let thenRealm be
// getThenRealmResult.[[Value]].
// 4. Otherwise, let thenRealm be null.
//
// The only cases where |thenRealm| can be null is when |then| is a revoked
// Proxy object, which would throw when it is called anyway. So instead of
// setting the context to null as the spec does, we just use the current
// realm.
const thenContext: Context = ExtractHandlerContext(then);
const nativeContext = LoadNativeContext(thenContext);
// 1. Let job be a new Job abstract closure with no parameters that
// captures promiseToResolve, thenable, and then...
// 5. Return { [[Job]]: job, [[Realm]]: thenRealm }.
return new PromiseResolveThenableJobTask{
map: PromiseResolveThenableJobTaskMapConstant(),
context: nativeContext,
promise_to_resolve: promiseToResolve,
thenable,
then
};
}
struct InvokeThenOneArgFunctor {
transitioning
macro Call(
nativeContext: NativeContext, then: JSAny, receiver: JSAny, arg1: JSAny,
_arg2: JSAny): JSAny {
return Call(nativeContext, then, receiver, arg1);
}
}
struct InvokeThenTwoArgFunctor {
transitioning
macro Call(
nativeContext: NativeContext, then: JSAny, receiver: JSAny, arg1: JSAny,
arg2: JSAny): JSAny {
return Call(nativeContext, then, receiver, arg1, arg2);
}
}
transitioning
macro InvokeThen<F: type>(implicit context: Context)(
nativeContext: NativeContext, receiver: JSAny, arg1: JSAny, arg2: JSAny,
callFunctor: F): JSAny {
// We can skip the "then" lookup on {receiver} if it's [[Prototype]]
// is the (initial) Promise.prototype and the Promise#then protector
// is intact, as that guards the lookup path for the "then" property
// on JSPromise instances which have the (initial) %PromisePrototype%.
if (!Is<Smi>(receiver) &&
IsPromiseThenLookupChainIntact(
nativeContext, UnsafeCast<HeapObject>(receiver).map)) {
const then =
*NativeContextSlot(nativeContext, ContextSlot::PROMISE_THEN_INDEX);
return callFunctor.Call(nativeContext, then, receiver, arg1, arg2);
} else
deferred {
const then = UnsafeCast<JSAny>(GetProperty(receiver, kThenString));
return callFunctor.Call(nativeContext, then, receiver, arg1, arg2);
}
}
transitioning
macro InvokeThen(implicit context: Context)(
nativeContext: NativeContext, receiver: JSAny, arg: JSAny): JSAny {
return InvokeThen(
nativeContext, receiver, arg, Undefined, InvokeThenOneArgFunctor{});
}
transitioning
macro InvokeThen(implicit context: Context)(
nativeContext: NativeContext, receiver: JSAny, arg1: JSAny,
arg2: JSAny): JSAny {
return InvokeThen(
nativeContext, receiver, arg1, arg2, InvokeThenTwoArgFunctor{});
}
transitioning
macro BranchIfAccessCheckFailed(implicit context: Context)(
nativeContext: NativeContext, promiseConstructor: JSAny,
executor: JSAny): void labels IfNoAccess {
try {
// If executor is a bound function, load the bound function until we've
// reached an actual function.
let foundExecutor = executor;
while (true) {
typeswitch (foundExecutor) {
case (f: JSFunction): {
// Load the context from the function and compare it to the Promise
// constructor's context. If they match, everything is fine,
// otherwise, bail out to the runtime.
const functionContext = f.context;
const nativeFunctionContext = LoadNativeContext(functionContext);
if (TaggedEqual(nativeContext, nativeFunctionContext)) {
goto HasAccess;
} else {
goto CallRuntime;
}
}
case (b: JSBoundFunction): {
foundExecutor = b.bound_target_function;
}
case (Object): {
goto CallRuntime;
}
}
}
} label CallRuntime deferred {
const result = runtime::AllowDynamicFunction(promiseConstructor);
if (result != True) {
goto IfNoAccess;
}
} label HasAccess {}
}
}