• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {
9extern transitioning runtime
10AllowDynamicFunction(implicit context: Context)(JSAny): JSAny;
11
12extern transitioning runtime ThrowNoAccess(implicit context: Context)(): never;
13
14extern transitioning runtime
15ReportMessageFromMicrotask(implicit context: Context)(JSAny): JSAny;
16}
17
18// Unsafe functions that should be used very carefully.
19namespace promise_internal {
20extern macro PromiseBuiltinsAssembler::ZeroOutEmbedderOffsets(JSPromise): void;
21
22extern macro PromiseBuiltinsAssembler::AllocateJSPromise(Context): HeapObject;
23}
24
25extern macro
26PromiseBuiltinsAssembler::IsContextPromiseHookEnabled(uint32): bool;
27
28extern macro
29PromiseBuiltinsAssembler::IsIsolatePromiseHookEnabled(uint32): bool;
30
31extern macro
32PromiseBuiltinsAssembler::PromiseHookFlags(): uint32;
33
34namespace promise {
35extern macro IsFunctionWithPrototypeSlotMap(Map): bool;
36
37@export
38macro PromiseHasHandler(promise: JSPromise): bool {
39  return promise.HasHandler();
40}
41
42@export
43macro PromiseInit(promise: JSPromise): void {
44  promise.reactions_or_result = kZero;
45  promise.flags = SmiTag(JSPromiseFlags{
46    status: PromiseState::kPending,
47    has_handler: false,
48    handled_hint: false,
49    is_silent: false,
50    async_task_id: 0
51  });
52  promise_internal::ZeroOutEmbedderOffsets(promise);
53}
54
55macro InnerNewJSPromise(implicit context: Context)(): JSPromise {
56  const promiseFun = *NativeContextSlot(ContextSlot::PROMISE_FUNCTION_INDEX);
57  dcheck(IsFunctionWithPrototypeSlotMap(promiseFun.map));
58  const promiseMap = UnsafeCast<Map>(promiseFun.prototype_or_initial_map);
59  const promiseHeapObject = promise_internal::AllocateJSPromise(context);
60  *UnsafeConstCast(&promiseHeapObject.map) = promiseMap;
61  const promise = UnsafeCast<JSPromise>(promiseHeapObject);
62  promise.properties_or_hash = kEmptyFixedArray;
63  promise.elements = kEmptyFixedArray;
64  promise.reactions_or_result = kZero;
65  promise.flags = SmiTag(JSPromiseFlags{
66    status: PromiseState::kPending,
67    has_handler: false,
68    handled_hint: false,
69    is_silent: false,
70    async_task_id: 0
71  });
72  return promise;
73}
74
75macro NewPromiseFulfillReactionJobTask(implicit context: Context)(
76    handlerContext: Context, argument: Object, handler: Callable|Undefined,
77    promiseOrCapability: JSPromise|PromiseCapability|
78    Undefined): PromiseFulfillReactionJobTask {
79  const nativeContext = LoadNativeContext(handlerContext);
80  return new PromiseFulfillReactionJobTask{
81    map: PromiseFulfillReactionJobTaskMapConstant(),
82    argument,
83    context: handlerContext,
84    handler,
85    promise_or_capability: promiseOrCapability,
86    continuation_preserved_embedder_data:
87        *ContextSlot(
88        nativeContext, ContextSlot::CONTINUATION_PRESERVED_EMBEDDER_DATA_INDEX)
89  };
90}
91
92macro NewPromiseRejectReactionJobTask(implicit context: Context)(
93    handlerContext: Context, argument: Object, handler: Callable|Undefined,
94    promiseOrCapability: JSPromise|PromiseCapability|
95    Undefined): PromiseRejectReactionJobTask {
96  const nativeContext = LoadNativeContext(handlerContext);
97  return new PromiseRejectReactionJobTask{
98    map: PromiseRejectReactionJobTaskMapConstant(),
99    argument,
100    context: handlerContext,
101    handler,
102    promise_or_capability: promiseOrCapability,
103    continuation_preserved_embedder_data:
104        *ContextSlot(
105        nativeContext, ContextSlot::CONTINUATION_PRESERVED_EMBEDDER_DATA_INDEX)
106  };
107}
108
109@export
110transitioning macro RunContextPromiseHookInit(implicit context: Context)(
111    promise: JSPromise, parent: Object): void {
112  const maybeHook = *NativeContextSlot(
113      ContextSlot::PROMISE_HOOK_INIT_FUNCTION_INDEX);
114  const hook = Cast<Callable>(maybeHook) otherwise return;
115  const parentObject = Is<JSPromise>(parent) ? Cast<JSPromise>(parent)
116      otherwise unreachable: Undefined;
117
118  try {
119    Call(context, hook, Undefined, promise, parentObject);
120  } catch (e, _message) {
121    runtime::ReportMessageFromMicrotask(e);
122  }
123}
124
125@export
126transitioning macro RunContextPromiseHookResolve(implicit context: Context)(
127    promise: JSPromise): void {
128  // Use potentially unused variables.
129  const _unusedPromise = promise;
130  @if(V8_ENABLE_JAVASCRIPT_PROMISE_HOOKS) {
131    RunContextPromiseHook(
132        ContextSlot::PROMISE_HOOK_RESOLVE_FUNCTION_INDEX, promise,
133        PromiseHookFlags());
134  }
135}
136
137@export
138transitioning macro RunContextPromiseHookResolve(implicit context: Context)(
139    promise: JSPromise, flags: uint32): void {
140  RunContextPromiseHook(
141      ContextSlot::PROMISE_HOOK_RESOLVE_FUNCTION_INDEX, promise, flags);
142}
143
144@export
145transitioning macro RunContextPromiseHookBefore(implicit context: Context)(
146    promiseOrCapability: JSPromise|PromiseCapability|Undefined): void {
147  // Use potentially unused variables.
148  const _unusedPromiseOrCapability = promiseOrCapability;
149  @if(V8_ENABLE_JAVASCRIPT_PROMISE_HOOKS) {
150    RunContextPromiseHook(
151        ContextSlot::PROMISE_HOOK_BEFORE_FUNCTION_INDEX, promiseOrCapability,
152        PromiseHookFlags());
153  }
154}
155
156@export
157transitioning macro RunContextPromiseHookBefore(implicit context: Context)(
158    promiseOrCapability: JSPromise|PromiseCapability|Undefined, flags: uint32):
159    void {
160  RunContextPromiseHook(
161      ContextSlot::PROMISE_HOOK_BEFORE_FUNCTION_INDEX, promiseOrCapability,
162      flags);
163}
164
165@export
166transitioning macro RunContextPromiseHookAfter(implicit context: Context)(
167    promiseOrCapability: JSPromise|PromiseCapability|Undefined): void {
168  // Use potentially unused variables.
169  const _unusedPromiseOrCapability = promiseOrCapability;
170  @if(V8_ENABLE_JAVASCRIPT_PROMISE_HOOKS) {
171    RunContextPromiseHook(
172        ContextSlot::PROMISE_HOOK_AFTER_FUNCTION_INDEX, promiseOrCapability,
173        PromiseHookFlags());
174  }
175}
176
177@export
178transitioning macro RunContextPromiseHookAfter(implicit context: Context)(
179    promiseOrCapability: JSPromise|PromiseCapability|Undefined, flags: uint32):
180    void {
181  RunContextPromiseHook(
182      ContextSlot::PROMISE_HOOK_AFTER_FUNCTION_INDEX, promiseOrCapability,
183      flags);
184}
185
186transitioning macro RunContextPromiseHook(implicit context: Context)(
187    slot: Slot<NativeContext, Undefined|Callable>,
188    promiseOrCapability: JSPromise|PromiseCapability|Undefined,
189    flags: uint32): void {
190  // Use potentially unused variables.
191  const _unusedSlot = slot;
192  const _unusedPromiseOrCapability = promiseOrCapability;
193  const _unusedFlags = flags;
194  @if(V8_ENABLE_JAVASCRIPT_PROMISE_HOOKS) {
195    if (!IsContextPromiseHookEnabled(flags)) return;
196    const maybeHook = *NativeContextSlot(slot);
197    const hook = Cast<Callable>(maybeHook) otherwise return;
198
199    let promise: JSPromise;
200    typeswitch (promiseOrCapability) {
201      case (jspromise: JSPromise): {
202        promise = jspromise;
203      }
204      case (capability: PromiseCapability): {
205        promise = Cast<JSPromise>(capability.promise) otherwise return;
206      }
207      case (Undefined): {
208        return;
209      }
210    }
211
212    try {
213      Call(context, hook, Undefined, promise);
214    } catch (e, _message) {
215      runtime::ReportMessageFromMicrotask(e);
216    }
217  }
218}
219
220transitioning macro RunAnyPromiseHookInit(implicit context: Context)(
221    promise: JSPromise, parent: Object): void {
222  const promiseHookFlags = PromiseHookFlags();
223  // Fast return if no hooks are set.
224  if (promiseHookFlags == 0) return;
225  @if(V8_ENABLE_JAVASCRIPT_PROMISE_HOOKS) {
226    if (IsContextPromiseHookEnabled(promiseHookFlags)) {
227      RunContextPromiseHookInit(promise, parent);
228    }
229  }
230  if (IsIsolatePromiseHookEnabled(promiseHookFlags)) {
231    runtime::PromiseHookInit(promise, parent);
232  }
233}
234
235// These allocate and initialize a promise with pending state and
236// undefined fields.
237//
238// This uses the given parent as the parent promise for the promise
239// init hook.
240@export
241transitioning macro NewJSPromise(implicit context: Context)(parent: Object):
242    JSPromise {
243  const instance = InnerNewJSPromise();
244  PromiseInit(instance);
245  RunAnyPromiseHookInit(instance, parent);
246  return instance;
247}
248
249// This uses undefined as the parent promise for the promise init
250// hook.
251@export
252transitioning macro NewJSPromise(implicit context: Context)(): JSPromise {
253  return NewJSPromise(Undefined);
254}
255
256// This allocates and initializes a promise with the given state and
257// fields.
258@export
259transitioning macro NewJSPromise(implicit context: Context)(
260    status: constexpr PromiseState, result: JSAny): JSPromise {
261  dcheck(status != PromiseState::kPending);
262
263  const instance = InnerNewJSPromise();
264  instance.reactions_or_result = result;
265  instance.SetStatus(status);
266  promise_internal::ZeroOutEmbedderOffsets(instance);
267  RunAnyPromiseHookInit(instance, Undefined);
268  return instance;
269}
270
271macro NewPromiseReaction(implicit context: Context)(
272    handlerContext: Context, next: Zero|PromiseReaction,
273    promiseOrCapability: JSPromise|PromiseCapability|Undefined,
274    fulfillHandler: Callable|Undefined,
275    rejectHandler: Callable|Undefined): PromiseReaction {
276  const nativeContext = LoadNativeContext(handlerContext);
277  return new PromiseReaction{
278    map: PromiseReactionMapConstant(),
279    next: next,
280    reject_handler: rejectHandler,
281    fulfill_handler: fulfillHandler,
282    promise_or_capability: promiseOrCapability,
283    continuation_preserved_embedder_data:
284        *ContextSlot(
285        nativeContext, ContextSlot::CONTINUATION_PRESERVED_EMBEDDER_DATA_INDEX)
286  };
287}
288
289extern macro PromiseResolveThenableJobTaskMapConstant(): Map;
290
291// https://tc39.es/ecma262/#sec-newpromiseresolvethenablejob
292macro NewPromiseResolveThenableJobTask(implicit context: Context)(
293    promiseToResolve: JSPromise, thenable: JSReceiver,
294    then: Callable): PromiseResolveThenableJobTask {
295  // 2. Let getThenRealmResult be GetFunctionRealm(then).
296  // 3. If getThenRealmResult is a normal completion, then let thenRealm be
297  //    getThenRealmResult.[[Value]].
298  // 4. Otherwise, let thenRealm be null.
299  //
300  // The only cases where |thenRealm| can be null is when |then| is a revoked
301  // Proxy object, which would throw when it is called anyway. So instead of
302  // setting the context to null as the spec does, we just use the current
303  // realm.
304  const thenContext: Context = ExtractHandlerContext(then);
305  const nativeContext = LoadNativeContext(thenContext);
306
307  // 1. Let job be a new Job abstract closure with no parameters that
308  //    captures promiseToResolve, thenable, and then...
309  // 5. Return { [[Job]]: job, [[Realm]]: thenRealm }.
310  return new PromiseResolveThenableJobTask{
311    map: PromiseResolveThenableJobTaskMapConstant(),
312    context: nativeContext,
313    promise_to_resolve: promiseToResolve,
314    thenable,
315    then
316  };
317}
318
319struct InvokeThenOneArgFunctor {
320  transitioning
321  macro Call(
322      nativeContext: NativeContext, then: JSAny, receiver: JSAny, arg1: JSAny,
323      _arg2: JSAny): JSAny {
324    return Call(nativeContext, then, receiver, arg1);
325  }
326}
327
328struct InvokeThenTwoArgFunctor {
329  transitioning
330  macro Call(
331      nativeContext: NativeContext, then: JSAny, receiver: JSAny, arg1: JSAny,
332      arg2: JSAny): JSAny {
333    return Call(nativeContext, then, receiver, arg1, arg2);
334  }
335}
336
337transitioning
338macro InvokeThen<F: type>(implicit context: Context)(
339    nativeContext: NativeContext, receiver: JSAny, arg1: JSAny, arg2: JSAny,
340    callFunctor: F): JSAny {
341  // We can skip the "then" lookup on {receiver} if it's [[Prototype]]
342  // is the (initial) Promise.prototype and the Promise#then protector
343  // is intact, as that guards the lookup path for the "then" property
344  // on JSPromise instances which have the (initial) %PromisePrototype%.
345  if (!Is<Smi>(receiver) &&
346      IsPromiseThenLookupChainIntact(
347          nativeContext, UnsafeCast<HeapObject>(receiver).map)) {
348    const then =
349        *NativeContextSlot(nativeContext, ContextSlot::PROMISE_THEN_INDEX);
350    return callFunctor.Call(nativeContext, then, receiver, arg1, arg2);
351  } else
352    deferred {
353      const then = UnsafeCast<JSAny>(GetProperty(receiver, kThenString));
354      return callFunctor.Call(nativeContext, then, receiver, arg1, arg2);
355    }
356}
357
358transitioning
359macro InvokeThen(implicit context: Context)(
360    nativeContext: NativeContext, receiver: JSAny, arg: JSAny): JSAny {
361  return InvokeThen(
362      nativeContext, receiver, arg, Undefined, InvokeThenOneArgFunctor{});
363}
364
365transitioning
366macro InvokeThen(implicit context: Context)(
367    nativeContext: NativeContext, receiver: JSAny, arg1: JSAny,
368    arg2: JSAny): JSAny {
369  return InvokeThen(
370      nativeContext, receiver, arg1, arg2, InvokeThenTwoArgFunctor{});
371}
372
373transitioning
374macro BranchIfAccessCheckFailed(implicit context: Context)(
375    nativeContext: NativeContext, promiseConstructor: JSAny,
376    executor: JSAny): void labels IfNoAccess {
377  try {
378    // If executor is a bound function, load the bound function until we've
379    // reached an actual function.
380    let foundExecutor = executor;
381    while (true) {
382      typeswitch (foundExecutor) {
383        case (f: JSFunction): {
384          // Load the context from the function and compare it to the Promise
385          // constructor's context. If they match, everything is fine,
386          // otherwise, bail out to the runtime.
387          const functionContext = f.context;
388          const nativeFunctionContext = LoadNativeContext(functionContext);
389          if (TaggedEqual(nativeContext, nativeFunctionContext)) {
390            goto HasAccess;
391          } else {
392            goto CallRuntime;
393          }
394        }
395        case (b: JSBoundFunction): {
396          foundExecutor = b.bound_target_function;
397        }
398        case (Object): {
399          goto CallRuntime;
400        }
401      }
402    }
403  } label CallRuntime deferred {
404    const result = runtime::AllowDynamicFunction(promiseConstructor);
405    if (result != True) {
406      goto IfNoAccess;
407    }
408  } label HasAccess {}
409}
410}
411