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 }