tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

AsyncFunction.cpp (13061B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
      2 * vim: set ts=8 sts=2 et sw=2 tw=80:
      3 * This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "vm/AsyncFunction.h"
      8 
      9 #include "mozilla/Maybe.h"
     10 
     11 #include "jsapi.h"
     12 
     13 #include "builtin/ModuleObject.h"
     14 #include "builtin/Promise.h"
     15 #include "js/Wrapper.h"
     16 #include "proxy/DeadObjectProxy.h"
     17 #include "vm/FunctionFlags.h"  // js::FunctionFlags
     18 #include "vm/GeneratorObject.h"
     19 #include "vm/GlobalObject.h"
     20 #include "vm/Interpreter.h"
     21 #include "vm/Modules.h"
     22 #include "vm/NativeObject.h"
     23 #include "vm/PromiseObject.h"  // js::PromiseObject
     24 #include "vm/Realm.h"
     25 #include "vm/SelfHosting.h"
     26 
     27 #include "vm/JSContext-inl.h"
     28 #include "vm/JSObject-inl.h"
     29 
     30 using namespace js;
     31 
     32 using mozilla::Maybe;
     33 
     34 static JSObject* CreateAsyncFunction(JSContext* cx, JSProtoKey key) {
     35  RootedObject proto(cx, &cx->global()->getFunctionConstructor());
     36  Handle<PropertyName*> name = cx->names().AsyncFunction;
     37  return NewFunctionWithProto(cx, AsyncFunctionConstructor, 1,
     38                              FunctionFlags::NATIVE_CTOR, nullptr, name, proto,
     39                              gc::AllocKind::FUNCTION, TenuredObject);
     40 }
     41 
     42 static JSObject* CreateAsyncFunctionPrototype(JSContext* cx, JSProtoKey key) {
     43  return NewTenuredObjectWithFunctionPrototype(cx, cx->global());
     44 }
     45 
     46 static bool AsyncFunctionClassFinish(JSContext* cx, HandleObject asyncFunction,
     47                                     HandleObject asyncFunctionProto) {
     48  // Change the "constructor" property to non-writable before adding any other
     49  // properties, so it's still the last property and can be modified without a
     50  // dictionary-mode transition.
     51  MOZ_ASSERT(asyncFunctionProto->as<NativeObject>().getLastProperty().key() ==
     52             NameToId(cx->names().constructor));
     53  MOZ_ASSERT(!asyncFunctionProto->as<NativeObject>().inDictionaryMode());
     54 
     55  RootedValue asyncFunctionVal(cx, ObjectValue(*asyncFunction));
     56  if (!DefineDataProperty(cx, asyncFunctionProto, cx->names().constructor,
     57                          asyncFunctionVal, JSPROP_READONLY)) {
     58    return false;
     59  }
     60  MOZ_ASSERT(!asyncFunctionProto->as<NativeObject>().inDictionaryMode());
     61 
     62  return DefineToStringTag(cx, asyncFunctionProto, cx->names().AsyncFunction);
     63 }
     64 
     65 static const ClassSpec AsyncFunctionClassSpec = {
     66    CreateAsyncFunction,
     67    CreateAsyncFunctionPrototype,
     68    nullptr,
     69    nullptr,
     70    nullptr,
     71    nullptr,
     72    AsyncFunctionClassFinish,
     73    ClassSpec::DontDefineConstructor,
     74 };
     75 
     76 const JSClass js::AsyncFunctionClass = {
     77    "AsyncFunction",
     78    0,
     79    JS_NULL_CLASS_OPS,
     80    &AsyncFunctionClassSpec,
     81 };
     82 
     83 enum class ResumeKind { Normal, Throw };
     84 
     85 /**
     86 * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
     87 *
     88 * Await in async function
     89 * https://tc39.es/ecma262/#await
     90 *
     91 * Unified implementation of
     92 *
     93 * Step 3. fulfilledClosure Abstract Closure.
     94 * Step 5. rejectedClosure Abstract Closure.
     95 */
     96 static bool AsyncFunctionResume(JSContext* cx,
     97                                Handle<AsyncFunctionGeneratorObject*> generator,
     98                                ResumeKind kind, HandleValue valueOrReason) {
     99  // We're enqueuing the promise job for Await before suspending the execution
    100  // of the async function. So when either the debugger or OOM errors terminate
    101  // the execution after JSOp::AsyncAwait, but before JSOp::Await, we're in an
    102  // inconsistent state, because we don't have a resume index set and therefore
    103  // don't know where to resume the async function. Return here in that case.
    104  if (generator->isClosed()) {
    105    return true;
    106  }
    107 
    108  // The debugger sets the async function's generator object into the "running"
    109  // state while firing debugger events to ensure the debugger can't re-enter
    110  // the async function, cf. |AutoSetGeneratorRunning| in Debugger.cpp. Catch
    111  // this case here by checking if the generator is already runnning.
    112  if (generator->isRunning()) {
    113    return true;
    114  }
    115 
    116  Rooted<PromiseObject*> resultPromise(cx, generator->promise());
    117 
    118  RootedObject stack(cx);
    119  Maybe<JS::AutoSetAsyncStackForNewCalls> asyncStack;
    120  if (JSObject* allocationSite = resultPromise->allocationSite()) {
    121    // The promise is created within the activation of the async function, so
    122    // use the parent frame as the starting point for async stacks.
    123    stack = allocationSite->as<SavedFrame>().getParent();
    124    if (stack) {
    125      asyncStack.emplace(
    126          cx, stack, "async",
    127          JS::AutoSetAsyncStackForNewCalls::AsyncCallKind::EXPLICIT);
    128    }
    129  }
    130 
    131  MOZ_ASSERT(generator->isSuspended(),
    132             "non-suspended generator when resuming async function");
    133 
    134  // Step {3,5}.a. Let prevContext be the running execution context.
    135  // Step {3,5}.b. Suspend prevContext.
    136  // Step {3,5}.c. Push asyncContext onto the execution context stack;
    137  //               asyncContext is now the running execution context.
    138  //
    139  // fulfilledClosure
    140  // Step 3.d. Resume the suspended evaluation of asyncContext using
    141  //           NormalCompletion(value) as the result of the operation that
    142  //           suspended it.
    143  //
    144  // rejectedClosure
    145  // Step 5.d. Resume the suspended evaluation of asyncContext using
    146  //           ThrowCompletion(reason) as the result of the operation that
    147  //           suspended it.
    148  //
    149  // Execution context switching is handled in generator.
    150  Handle<PropertyName*> funName = kind == ResumeKind::Normal
    151                                      ? cx->names().AsyncFunctionNext
    152                                      : cx->names().AsyncFunctionThrow;
    153  FixedInvokeArgs<1> args(cx);
    154  args[0].set(valueOrReason);
    155  RootedValue generatorOrValue(cx, ObjectValue(*generator));
    156  MOZ_RELEASE_ASSERT(cx->realm() == generator->nonCCWRealm());
    157  if (!CallSelfHostedFunction(cx, funName, generatorOrValue, args,
    158                              &generatorOrValue)) {
    159    if (!generator->isClosed()) {
    160      generator->setClosed(cx);
    161    }
    162 
    163    // Handle the OOM case mentioned above.
    164    if (resultPromise->state() == JS::PromiseState::Pending &&
    165        cx->isExceptionPending()) {
    166      RootedValue exn(cx);
    167      if (!GetAndClearException(cx, &exn)) {
    168        return false;
    169      }
    170      return AsyncFunctionThrown(cx, resultPromise, exn);
    171    }
    172    return false;
    173  }
    174 
    175  // Step {3,f}.e. Assert: When we reach this step, asyncContext has already
    176  //               been removed from the execution context stack and
    177  //               prevContext is the currently running execution context.
    178  // Step {3,f}.f. Return undefined.
    179  MOZ_ASSERT_IF(generator->isClosed(), generatorOrValue.isObject());
    180  MOZ_ASSERT_IF(generator->isClosed(),
    181                &generatorOrValue.toObject() == resultPromise);
    182  MOZ_ASSERT_IF(!generator->isClosed(), generator->isAfterAwait());
    183 
    184  return true;
    185 }
    186 
    187 /**
    188 * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
    189 *
    190 * Await in async function
    191 * https://tc39.es/ecma262/#await
    192 *
    193 * Step 3. fulfilledClosure Abstract Closure.
    194 */
    195 [[nodiscard]] bool js::AsyncFunctionAwaitedFulfilled(
    196    JSContext* cx, Handle<AsyncFunctionGeneratorObject*> generator,
    197    HandleValue value) {
    198  return AsyncFunctionResume(cx, generator, ResumeKind::Normal, value);
    199 }
    200 
    201 /**
    202 * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
    203 *
    204 * Await in async function
    205 * https://tc39.es/ecma262/#await
    206 *
    207 * Step 5. rejectedClosure Abstract Closure.
    208 */
    209 [[nodiscard]] bool js::AsyncFunctionAwaitedRejected(
    210    JSContext* cx, Handle<AsyncFunctionGeneratorObject*> generator,
    211    HandleValue reason) {
    212  return AsyncFunctionResume(cx, generator, ResumeKind::Throw, reason);
    213 }
    214 
    215 JSObject* js::AsyncFunctionResolve(
    216    JSContext* cx, Handle<AsyncFunctionGeneratorObject*> generator,
    217    HandleValue value) {
    218  Rooted<PromiseObject*> promise(cx, generator->promise());
    219  if (!AsyncFunctionReturned(cx, promise, value)) {
    220    return nullptr;
    221  }
    222  return promise;
    223 }
    224 
    225 JSObject* js::AsyncFunctionReject(
    226    JSContext* cx, Handle<AsyncFunctionGeneratorObject*> generator,
    227    HandleValue reason, HandleValue stack) {
    228  MOZ_ASSERT(stack.isObjectOrNull());
    229  Rooted<PromiseObject*> promise(cx, generator->promise());
    230  Rooted<SavedFrame*> unwrappedRejectionStack(cx);
    231  if (stack.isObject()) {
    232    MOZ_ASSERT(UncheckedUnwrap(&stack.toObject())->is<SavedFrame>() ||
    233               IsDeadProxyObject(&stack.toObject()));
    234    unwrappedRejectionStack = stack.toObject().maybeUnwrapIf<SavedFrame>();
    235  }
    236  if (!AsyncFunctionThrown(cx, promise, reason, unwrappedRejectionStack)) {
    237    return nullptr;
    238  }
    239  return promise;
    240 }
    241 
    242 const JSClass AsyncFunctionGeneratorObject::class_ = {
    243    "AsyncFunctionGenerator",
    244    JSCLASS_HAS_RESERVED_SLOTS(AsyncFunctionGeneratorObject::RESERVED_SLOTS),
    245    &classOps_,
    246 };
    247 
    248 const JSClassOps AsyncFunctionGeneratorObject::classOps_ = {
    249    nullptr,                                   // addProperty
    250    nullptr,                                   // delProperty
    251    nullptr,                                   // enumerate
    252    nullptr,                                   // newEnumerate
    253    nullptr,                                   // resolve
    254    nullptr,                                   // mayResolve
    255    nullptr,                                   // finalize
    256    nullptr,                                   // call
    257    nullptr,                                   // construct
    258    CallTraceMethod<AbstractGeneratorObject>,  // trace
    259 };
    260 
    261 AsyncFunctionGeneratorObject* AsyncFunctionGeneratorObject::create(
    262    JSContext* cx, HandleFunction fun) {
    263  MOZ_ASSERT(fun->isAsync() && !fun->isGenerator());
    264 
    265  Rooted<PromiseObject*> resultPromise(cx, CreatePromiseObjectForAsync(cx));
    266  if (!resultPromise) {
    267    return nullptr;
    268  }
    269 
    270  auto* obj = NewBuiltinClassInstance<AsyncFunctionGeneratorObject>(cx);
    271  if (!obj) {
    272    return nullptr;
    273  }
    274  obj->initFixedSlot(PROMISE_SLOT, ObjectValue(*resultPromise));
    275 
    276  // Starts in the running state.
    277  obj->setResumeIndex(AbstractGeneratorObject::RESUME_INDEX_RUNNING);
    278 
    279  return obj;
    280 }
    281 
    282 JSFunction* NewHandler(JSContext* cx, Native handler,
    283                       JS::Handle<JSObject*> target) {
    284  cx->check(target);
    285 
    286  JS::Handle<PropertyName*> funName = cx->names().empty_;
    287  JSFunction* handlerFun = NewNativeFunction(
    288      cx, handler, 0, funName, gc::AllocKind::FUNCTION_EXTENDED, GenericObject);
    289  if (!handlerFun) {
    290    return nullptr;
    291  }
    292  handlerFun->setExtendedSlot(FunctionExtended::MODULE_SLOT,
    293                              JS::ObjectValue(*target));
    294  return handlerFun;
    295 }
    296 
    297 static bool AsyncModuleExecutionFulfilledHandler(JSContext* cx, unsigned argc,
    298                                                 Value* vp) {
    299  CallArgs args = CallArgsFromVp(argc, vp);
    300  JSFunction& func = args.callee().as<JSFunction>();
    301 
    302  Rooted<ModuleObject*> module(
    303      cx, &func.getExtendedSlot(FunctionExtended::MODULE_SLOT)
    304               .toObject()
    305               .as<ModuleObject>());
    306  AsyncModuleExecutionFulfilled(cx, module);
    307  args.rval().setUndefined();
    308  return true;
    309 }
    310 
    311 static bool AsyncModuleExecutionRejectedHandler(JSContext* cx, unsigned argc,
    312                                                Value* vp) {
    313  CallArgs args = CallArgsFromVp(argc, vp);
    314  JSFunction& func = args.callee().as<JSFunction>();
    315  Rooted<ModuleObject*> module(
    316      cx, &func.getExtendedSlot(FunctionExtended::MODULE_SLOT)
    317               .toObject()
    318               .as<ModuleObject>());
    319  AsyncModuleExecutionRejected(cx, module, args.get(0));
    320  args.rval().setUndefined();
    321  return true;
    322 }
    323 
    324 AsyncFunctionGeneratorObject* AsyncFunctionGeneratorObject::create(
    325    JSContext* cx, Handle<ModuleObject*> module) {
    326  // TODO: Module is currently hitching a ride with
    327  // AsyncFunctionGeneratorObject. The reason for this is we have some work in
    328  // the JITs that make use of this object when we hit AsyncAwait bytecode. At
    329  // the same time, top level await shares a lot of it's implementation with
    330  // AsyncFunction. I am not sure if the best thing to do here is inherit,
    331  // override, or do something else. Comments appreciated.
    332  MOZ_ASSERT(module->script()->isAsync());
    333 
    334  Rooted<PromiseObject*> resultPromise(cx, CreatePromiseObjectForAsync(cx));
    335  if (!resultPromise) {
    336    return nullptr;
    337  }
    338 
    339  Rooted<AsyncFunctionGeneratorObject*> obj(
    340      cx, NewBuiltinClassInstance<AsyncFunctionGeneratorObject>(cx));
    341  if (!obj) {
    342    return nullptr;
    343  }
    344  obj->initFixedSlot(PROMISE_SLOT, ObjectValue(*resultPromise));
    345 
    346  RootedObject onFulfilled(
    347      cx, NewHandler(cx, AsyncModuleExecutionFulfilledHandler, module));
    348  if (!onFulfilled) {
    349    return nullptr;
    350  }
    351 
    352  RootedObject onRejected(
    353      cx, NewHandler(cx, AsyncModuleExecutionRejectedHandler, module));
    354  if (!onRejected) {
    355    return nullptr;
    356  }
    357 
    358  if (!JS::AddPromiseReactionsIgnoringUnhandledRejection(
    359          cx, resultPromise, onFulfilled, onRejected)) {
    360    return nullptr;
    361  }
    362 
    363  // Starts in the running state.
    364  obj->setResumeIndex(AbstractGeneratorObject::RESUME_INDEX_RUNNING);
    365 
    366  return obj;
    367 }