1// Copyright 2019 the V8 project authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include 'src/builtins/builtins-promise.h'
6#include 'src/builtins/builtins-promise-gen.h'
7
8namespace runtime {
9  extern transitioning runtime
10  AllowDynamicFunction(implicit context: Context)(JSAny): JSAny;
11}
12
13// Unsafe functions that should be used very carefully.
14namespace promise_internal {
15  extern macro PromiseBuiltinsAssembler::ZeroOutEmbedderOffsets(JSPromise):
16      void;
17
18  extern macro PromiseBuiltinsAssembler::AllocateJSPromise(Context): HeapObject;
19}
20
21namespace promise {
22  extern macro IsFunctionWithPrototypeSlotMap(Map): bool;
23
24  @export
25  macro PromiseHasHandler(promise: JSPromise): bool {
26    return promise.HasHandler();
27  }
28
29  @export
30  macro PromiseInit(promise: JSPromise): void {
31    assert(PromiseState::kPending == 0);
32    promise.reactions_or_result = kZero;
33    promise.flags = 0;
34    promise_internal::ZeroOutEmbedderOffsets(promise);
35  }
36
37  macro InnerNewJSPromise(implicit context: Context)(): JSPromise {
38    const nativeContext = LoadNativeContext(context);
39    const promiseFun = UnsafeCast<JSFunction>(
40        nativeContext[NativeContextSlot::PROMISE_FUNCTION_INDEX]);
41    assert(IsFunctionWithPrototypeSlotMap(promiseFun.map));
42    const promiseMap = UnsafeCast<Map>(promiseFun.prototype_or_initial_map);
43    const promiseHeapObject = promise_internal::AllocateJSPromise(context);
44    * UnsafeConstCast(& promiseHeapObject.map) = promiseMap;
45    const promise = UnsafeCast<JSPromise>(promiseHeapObject);
46    promise.properties_or_hash = kEmptyFixedArray;
47    promise.elements = kEmptyFixedArray;
48    promise.reactions_or_result = kZero;
49    promise.flags = 0;
50    return promise;
51  }
52
53  macro NewPromiseFulfillReactionJobTask(implicit context: Context)(
54      handlerContext: Context, argument: Object, handler: Callable|Undefined,
55      promiseOrCapability: JSPromise|PromiseCapability|
56      Undefined): PromiseFulfillReactionJobTask {
57    const nativeContext = LoadNativeContext(handlerContext);
58    return new PromiseFulfillReactionJobTask{
59      map: PromiseFulfillReactionJobTaskMapConstant(),
60      argument,
61      context: handlerContext,
62      handler,
63      promise_or_capability: promiseOrCapability,
64      continuation_preserved_embedder_data: nativeContext
65          [NativeContextSlot::CONTINUATION_PRESERVED_EMBEDDER_DATA_INDEX]
66    };
67  }
68
69  macro NewPromiseRejectReactionJobTask(implicit context: Context)(
70      handlerContext: Context, argument: Object, handler: Callable|Undefined,
71      promiseOrCapability: JSPromise|PromiseCapability|
72      Undefined): PromiseRejectReactionJobTask {
73    const nativeContext = LoadNativeContext(handlerContext);
74    return new PromiseRejectReactionJobTask{
75      map: PromiseRejectReactionJobTaskMapConstant(),
76      argument,
77      context: handlerContext,
78      handler,
79      promise_or_capability: promiseOrCapability,
80      continuation_preserved_embedder_data: nativeContext
81          [NativeContextSlot::CONTINUATION_PRESERVED_EMBEDDER_DATA_INDEX]
82    };
83  }
84
85  // These allocate and initialize a promise with pending state and
86  // undefined fields.
87  //
88  // This uses the given parent as the parent promise for the promise
89  // init hook.
90  @export
91  transitioning macro NewJSPromise(implicit context: Context)(parent: Object):
92      JSPromise {
93    const instance = InnerNewJSPromise();
94    PromiseInit(instance);
95    if (IsPromiseHookEnabledOrHasAsyncEventDelegate()) {
96      runtime::PromiseHookInit(instance, parent);
97    }
98    return instance;
99  }
100
101  // This uses undefined as the parent promise for the promise init
102  // hook.
103  @export
104  transitioning macro NewJSPromise(implicit context: Context)(): JSPromise {
105    return NewJSPromise(Undefined);
106  }
107
108  // This allocates and initializes a promise with the given state and
109  // fields.
110  @export
111  transitioning macro NewJSPromise(implicit context: Context)(
112      status: constexpr PromiseState, result: JSAny): JSPromise {
113    assert(status != PromiseState::kPending);
114    assert(kJSPromiseStatusShift == 0);
115
116    const instance = InnerNewJSPromise();
117    instance.reactions_or_result = result;
118    instance.SetStatus(status);
119    promise_internal::ZeroOutEmbedderOffsets(instance);
120
121    if (IsPromiseHookEnabledOrHasAsyncEventDelegate()) {
122      runtime::PromiseHookInit(instance, Undefined);
123    }
124    return instance;
125  }
126
127  macro NewPromiseReaction(implicit context: Context)(
128      handlerContext: Context, next: Zero|PromiseReaction,
129      promiseOrCapability: JSPromise|PromiseCapability|Undefined,
130      fulfillHandler: Callable|Undefined,
131      rejectHandler: Callable|Undefined): PromiseReaction {
132    const nativeContext = LoadNativeContext(handlerContext);
133    return new PromiseReaction{
134      map: PromiseReactionMapConstant(),
135      next: next,
136      reject_handler: rejectHandler,
137      fulfill_handler: fulfillHandler,
138      promise_or_capability: promiseOrCapability,
139      continuation_preserved_embedder_data: nativeContext
140          [NativeContextSlot::CONTINUATION_PRESERVED_EMBEDDER_DATA_INDEX]
141    };
142  }
143
144  extern macro PromiseResolveThenableJobTaskMapConstant(): Map;
145
146  macro NewPromiseResolveThenableJobTask(implicit context: Context)(
147      promiseToResolve: JSPromise, then: JSReceiver, thenable: JSReceiver,
148      thenContext: Context): PromiseResolveThenableJobTask {
149    return new PromiseResolveThenableJobTask{
150      map: PromiseResolveThenableJobTaskMapConstant(),
151      context: thenContext,
152      promise_to_resolve: promiseToResolve,
153      then: then,
154      thenable: thenable
155    };
156  }
157
158  struct InvokeThenOneArgFunctor {
159    transitioning
160    macro Call(
161        nativeContext: NativeContext, then: JSAny, receiver: JSAny, arg1: JSAny,
162        _arg2: JSAny): JSAny {
163      return Call(nativeContext, then, receiver, arg1);
164    }
165  }
166
167  struct InvokeThenTwoArgFunctor {
168    transitioning
169    macro Call(
170        nativeContext: NativeContext, then: JSAny, receiver: JSAny, arg1: JSAny,
171        arg2: JSAny): JSAny {
172      return Call(nativeContext, then, receiver, arg1, arg2);
173    }
174  }
175
176  transitioning
177  macro InvokeThen<F: type>(implicit context: Context)(
178      nativeContext: NativeContext, receiver: JSAny, arg1: JSAny, arg2: JSAny,
179      callFunctor: F): JSAny {
180    // We can skip the "then" lookup on {receiver} if it's [[Prototype]]
181    // is the (initial) Promise.prototype and the Promise#then protector
182    // is intact, as that guards the lookup path for the "then" property
183    // on JSPromise instances which have the (initial) %PromisePrototype%.
184    if (!Is<Smi>(receiver) &&
185        IsPromiseThenLookupChainIntact(
186            nativeContext, UnsafeCast<HeapObject>(receiver).map)) {
187      const then = UnsafeCast<JSAny>(
188          nativeContext[NativeContextSlot::PROMISE_THEN_INDEX]);
189      return callFunctor.Call(nativeContext, then, receiver, arg1, arg2);
190    } else
191      deferred {
192        const then = UnsafeCast<JSAny>(GetProperty(receiver, kThenString));
193        return callFunctor.Call(nativeContext, then, receiver, arg1, arg2);
194      }
195  }
196
197  transitioning
198  macro InvokeThen(implicit context: Context)(
199      nativeContext: NativeContext, receiver: JSAny, arg: JSAny): JSAny {
200    return InvokeThen(
201        nativeContext, receiver, arg, Undefined, InvokeThenOneArgFunctor{});
202  }
203
204  transitioning
205  macro InvokeThen(implicit context: Context)(
206      nativeContext: NativeContext, receiver: JSAny, arg1: JSAny,
207      arg2: JSAny): JSAny {
208    return InvokeThen(
209        nativeContext, receiver, arg1, arg2, InvokeThenTwoArgFunctor{});
210  }
211
212  transitioning
213  macro BranchIfAccessCheckFailed(implicit context: Context)(
214      nativeContext: NativeContext, promiseConstructor: JSAny,
215      executor: JSAny): void labels IfNoAccess {
216    try {
217      // If executor is a bound function, load the bound function until we've
218      // reached an actual function.
219      let foundExecutor = executor;
220      while (true) {
221        typeswitch (foundExecutor) {
222          case (f: JSFunction): {
223            // Load the context from the function and compare it to the Promise
224            // constructor's context. If they match, everything is fine,
225            // otherwise, bail out to the runtime.
226            const functionContext = f.context;
227            const nativeFunctionContext = LoadNativeContext(functionContext);
228            if (TaggedEqual(nativeContext, nativeFunctionContext)) {
229              goto HasAccess;
230            } else {
231              goto CallRuntime;
232            }
233          }
234          case (b: JSBoundFunction): {
235            foundExecutor = b.bound_target_function;
236          }
237          case (Object): {
238            goto CallRuntime;
239          }
240        }
241      }
242    }
243    label CallRuntime deferred {
244      const result = runtime::AllowDynamicFunction(promiseConstructor);
245      if (result != True) {
246        goto IfNoAccess;
247      }
248    }
249    label HasAccess {}
250  }
251}
252