// 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; extern transitioning runtime ReportMessageFromMicrotask(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; } extern macro PromiseBuiltinsAssembler::IsContextPromiseHookEnabled(uint32): bool; extern macro PromiseBuiltinsAssembler::PromiseHookFlags(): uint32; 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, is_silent: 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, is_silent: 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) }; } @export transitioning macro RunContextPromiseHookInit(implicit context: Context)( promise: JSPromise, parent: Object) { const maybeHook = *NativeContextSlot( ContextSlot::PROMISE_HOOK_INIT_FUNCTION_INDEX); const hook = Cast<Callable>(maybeHook) otherwise return; const parentObject = Is<JSPromise>(parent) ? Cast<JSPromise>(parent) otherwise unreachable: Undefined; try { Call(context, hook, Undefined, promise, parentObject); } catch (e) { runtime::ReportMessageFromMicrotask(e); } } @export transitioning macro RunContextPromiseHookResolve(implicit context: Context)( promise: JSPromise) { RunContextPromiseHook( ContextSlot::PROMISE_HOOK_RESOLVE_FUNCTION_INDEX, promise, PromiseHookFlags()); } @export transitioning macro RunContextPromiseHookResolve(implicit context: Context)( promise: JSPromise, flags: uint32) { RunContextPromiseHook( ContextSlot::PROMISE_HOOK_RESOLVE_FUNCTION_INDEX, promise, flags); } @export transitioning macro RunContextPromiseHookBefore(implicit context: Context)( promiseOrCapability: JSPromise|PromiseCapability|Undefined) { RunContextPromiseHook( ContextSlot::PROMISE_HOOK_BEFORE_FUNCTION_INDEX, promiseOrCapability, PromiseHookFlags()); } @export transitioning macro RunContextPromiseHookBefore(implicit context: Context)( promiseOrCapability: JSPromise|PromiseCapability|Undefined, flags: uint32) { RunContextPromiseHook( ContextSlot::PROMISE_HOOK_BEFORE_FUNCTION_INDEX, promiseOrCapability, flags); } @export transitioning macro RunContextPromiseHookAfter(implicit context: Context)( promiseOrCapability: JSPromise|PromiseCapability|Undefined) { RunContextPromiseHook( ContextSlot::PROMISE_HOOK_AFTER_FUNCTION_INDEX, promiseOrCapability, PromiseHookFlags()); } @export transitioning macro RunContextPromiseHookAfter(implicit context: Context)( promiseOrCapability: JSPromise|PromiseCapability|Undefined, flags: uint32) { RunContextPromiseHook( ContextSlot::PROMISE_HOOK_AFTER_FUNCTION_INDEX, promiseOrCapability, flags); } transitioning macro RunContextPromiseHook(implicit context: Context)( slot: Slot<NativeContext, Undefined|Callable>, promiseOrCapability: JSPromise|PromiseCapability|Undefined, flags: uint32) { if (!IsContextPromiseHookEnabled(flags)) return; const maybeHook = *NativeContextSlot(slot); const hook = Cast<Callable>(maybeHook) otherwise return; let promise: JSPromise; typeswitch (promiseOrCapability) { case (jspromise: JSPromise): { promise = jspromise; } case (capability: PromiseCapability): { promise = Cast<JSPromise>(capability.promise) otherwise return; } case (Undefined): { return; } } try { Call(context, hook, Undefined, promise); } catch (e) { runtime::ReportMessageFromMicrotask(e); } } transitioning macro RunAnyPromiseHookInit(implicit context: Context)( promise: JSPromise, parent: Object) { const promiseHookFlags = PromiseHookFlags(); // Fast return if no hooks are set. if (promiseHookFlags == 0) return; if (IsContextPromiseHookEnabled(promiseHookFlags)) { RunContextPromiseHookInit(promise, parent); } if (IsIsolatePromiseHookEnabledOrHasAsyncEventDelegate(promiseHookFlags)) { runtime::PromiseHookInit(promise, parent); } } // 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); RunAnyPromiseHookInit(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); RunAnyPromiseHookInit(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 {} } }