| // 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 {} |
| } |
| } |