/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sts=4 et sw=4 tw=99: * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "vm/AsyncIteration.h" #include "jsarray.h" #include "jscompartment.h" #include "builtin/Promise.h" #include "vm/GeneratorObject.h" #include "vm/GlobalObject.h" #include "vm/Interpreter.h" #include "vm/SelfHosting.h" #include "jscntxtinlines.h" #include "jsobjinlines.h" #include "vm/NativeObject-inl.h" using namespace js; using namespace js::gc; #define UNWRAPPED_ASYNC_WRAPPED_SLOT 1 #define WRAPPED_ASYNC_UNWRAPPED_SLOT 0 // Async Iteration proposal 8.3.10 Runtime Semantics: EvaluateBody. static bool WrappedAsyncGenerator(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); RootedFunction wrapped(cx, &args.callee().as()); RootedValue unwrappedVal(cx, wrapped->getExtendedSlot(WRAPPED_ASYNC_UNWRAPPED_SLOT)); RootedFunction unwrapped(cx, &unwrappedVal.toObject().as()); RootedValue thisValue(cx, args.thisv()); // Step 1. RootedValue generatorVal(cx); InvokeArgs args2(cx); if (!args2.init(cx, argc)) return false; for (size_t i = 0, len = argc; i < len; i++) args2[i].set(args[i]); if (!Call(cx, unwrappedVal, thisValue, args2, &generatorVal)) return false; // Step 2. Rooted asyncGenObj( cx, AsyncGeneratorObject::create(cx, wrapped, generatorVal)); if (!asyncGenObj) return false; // Step 3 (skipped). // Done in AsyncGeneratorObject::create and generator. // Step 4. args.rval().setObject(*asyncGenObj); return true; } JSObject* js::WrapAsyncGeneratorWithProto(JSContext* cx, HandleFunction unwrapped, HandleObject proto) { MOZ_ASSERT(unwrapped->isAsync()); MOZ_ASSERT(proto, "We need an explicit prototype to avoid the default" "%FunctionPrototype% fallback in NewFunctionWithProto()."); // Create a new function with AsyncGeneratorPrototype, reusing the name and // the length of `unwrapped`. RootedAtom funName(cx, unwrapped->explicitName()); uint16_t length; if (!JSFunction::getLength(cx, unwrapped, &length)) return nullptr; RootedFunction wrapped(cx, NewFunctionWithProto(cx, WrappedAsyncGenerator, length, JSFunction::NATIVE_FUN, nullptr, funName, proto, AllocKind::FUNCTION_EXTENDED, TenuredObject)); if (!wrapped) return nullptr; if (unwrapped->hasCompileTimeName()) wrapped->setCompileTimeName(unwrapped->compileTimeName()); // Link them to each other to make GetWrappedAsyncGenerator and // GetUnwrappedAsyncGenerator work. unwrapped->setExtendedSlot(UNWRAPPED_ASYNC_WRAPPED_SLOT, ObjectValue(*wrapped)); wrapped->setExtendedSlot(WRAPPED_ASYNC_UNWRAPPED_SLOT, ObjectValue(*unwrapped)); return wrapped; } JSObject* js::WrapAsyncGenerator(JSContext* cx, HandleFunction unwrapped) { RootedObject proto(cx, GlobalObject::getOrCreateAsyncGenerator(cx, cx->global())); if (!proto) return nullptr; return WrapAsyncGeneratorWithProto(cx, unwrapped, proto); } bool js::IsWrappedAsyncGenerator(JSFunction* fun) { return fun->maybeNative() == WrappedAsyncGenerator; } JSFunction* js::GetWrappedAsyncGenerator(JSFunction* unwrapped) { MOZ_ASSERT(unwrapped->isAsync()); return &unwrapped->getExtendedSlot(UNWRAPPED_ASYNC_WRAPPED_SLOT).toObject().as(); } JSFunction* js::GetUnwrappedAsyncGenerator(JSFunction* wrapped) { MOZ_ASSERT(IsWrappedAsyncGenerator(wrapped)); JSFunction* unwrapped = &wrapped->getExtendedSlot(WRAPPED_ASYNC_UNWRAPPED_SLOT) .toObject().as(); MOZ_ASSERT(unwrapped->isAsync()); return unwrapped; } // Async Iteration proposal 4.1.1 Await Fulfilled Functions. MOZ_MUST_USE bool js::AsyncGeneratorAwaitedFulfilled(JSContext* cx, Handle asyncGenObj, HandleValue value) { return AsyncGeneratorResume(cx, asyncGenObj, CompletionKind::Normal, value); } // Async Iteration proposal 4.1.2 Await Rejected Functions. MOZ_MUST_USE bool js::AsyncGeneratorAwaitedRejected(JSContext* cx, Handle asyncGenObj, HandleValue reason) { return AsyncGeneratorResume(cx, asyncGenObj, CompletionKind::Throw, reason); } // Async Iteration proposal 11.4.3.7 step 8.d-e. MOZ_MUST_USE bool js::AsyncGeneratorYieldReturnAwaitedFulfilled(JSContext* cx, Handle asyncGenObj, HandleValue value) { return AsyncGeneratorResume(cx, asyncGenObj, CompletionKind::Return, value); } // Async Iteration proposal 11.4.3.7 step 8.d-e. MOZ_MUST_USE bool js::AsyncGeneratorYieldReturnAwaitedRejected(JSContext* cx, Handle asyncGenObj, HandleValue reason) { return AsyncGeneratorResume(cx, asyncGenObj, CompletionKind::Throw, reason); } const Class AsyncFromSyncIteratorObject::class_ = { "AsyncFromSyncIteratorObject", JSCLASS_HAS_RESERVED_SLOTS(AsyncFromSyncIteratorObject::Slots) }; // Async Iteration proposal 11.1.3.1. JSObject* js::CreateAsyncFromSyncIterator(JSContext* cx, HandleObject iter) { // Step 1 (implicit). // Done in bytecode emitted by emitAsyncIterator. // Steps 2-4. return AsyncFromSyncIteratorObject::create(cx, iter); } // Async Iteration proposal 11.1.3.1 steps 2-4. /* static */ JSObject* AsyncFromSyncIteratorObject::create(JSContext* cx, HandleObject iter) { // Step 2. RootedObject proto(cx, GlobalObject::getOrCreateAsyncFromSyncIteratorPrototype(cx, cx->global())); if (!proto) return nullptr; RootedObject obj(cx, NewNativeObjectWithGivenProto(cx, &class_, proto)); if (!obj) return nullptr; Handle asyncIter = obj.as(); // Step 3. asyncIter->setIterator(iter); // Step 4. return asyncIter; } // Async Iteration proposal 11.1.3.2.1 %AsyncFromSyncIteratorPrototype%.next. static bool AsyncFromSyncIteratorNext(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); return AsyncFromSyncIteratorMethod(cx, args, CompletionKind::Normal); } // Async Iteration proposal 11.1.3.2.2 %AsyncFromSyncIteratorPrototype%.return. static bool AsyncFromSyncIteratorReturn(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); return AsyncFromSyncIteratorMethod(cx, args, CompletionKind::Return); } // Async Iteration proposal 11.1.3.2.3 %AsyncFromSyncIteratorPrototype%.throw. static bool AsyncFromSyncIteratorThrow(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); return AsyncFromSyncIteratorMethod(cx, args, CompletionKind::Throw); } // Async Iteration proposal 11.4.1.2 AsyncGenerator.prototype.next. static bool AsyncGeneratorNext(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); // Steps 1-3. return AsyncGeneratorEnqueue(cx, args.thisv(), CompletionKind::Normal, args.get(0), args.rval()); } // Async Iteration proposal 11.4.1.3 AsyncGenerator.prototype.return. static bool AsyncGeneratorReturn(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); // Steps 1-3. return AsyncGeneratorEnqueue(cx, args.thisv(), CompletionKind::Return, args.get(0), args.rval()); } // Async Iteration proposal 11.4.1.4 AsyncGenerator.prototype.throw. static bool AsyncGeneratorThrow(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); // Steps 1-3. return AsyncGeneratorEnqueue(cx, args.thisv(), CompletionKind::Throw, args.get(0), args.rval()); } const Class AsyncGeneratorObject::class_ = { "AsyncGenerator", JSCLASS_HAS_RESERVED_SLOTS(AsyncGeneratorObject::Slots) }; // ES 2017 draft 9.1.13. template static JSObject* OrdinaryCreateFromConstructor(JSContext* cx, HandleFunction fun, ProtoGetter protoGetter, const Class* clasp) { // Step 1 (skipped). // Step 2. RootedValue protoVal(cx); if (!GetProperty(cx, fun, fun, cx->names().prototype, &protoVal)) return nullptr; RootedObject proto(cx, protoVal.isObject() ? &protoVal.toObject() : nullptr); if (!proto) { proto = protoGetter(cx, cx->global()); if (!proto) return nullptr; } // Step 3. return NewNativeObjectWithGivenProto(cx, clasp, proto); } /* static */ AsyncGeneratorObject* AsyncGeneratorObject::create(JSContext* cx, HandleFunction asyncGen, HandleValue generatorVal) { MOZ_ASSERT(generatorVal.isObject()); MOZ_ASSERT(generatorVal.toObject().is()); RootedObject obj( cx, OrdinaryCreateFromConstructor(cx, asyncGen, GlobalObject::getOrCreateAsyncGeneratorPrototype, &class_)); if (!obj) return nullptr; Handle asyncGenObj = obj.as(); // Async Iteration proposal 6.4.3.2 AsyncGeneratorStart. // Step 6. asyncGenObj->setGenerator(generatorVal); // Step 7. asyncGenObj->setSuspendedStart(); // Step 8. asyncGenObj->clearSingleQueueRequest(); return asyncGenObj; } static MOZ_MUST_USE bool InternalEnqueue(JSContext* cx, HandleArrayObject queue, HandleValue val) { uint32_t length; if (!GetLengthProperty(cx, queue, &length)) return false; if (length >= MAX_ARRAY_INDEX) { ReportOutOfMemory(cx); return false; } if (!DefineElement(cx, queue, length, val)) return false; return SetLengthProperty(cx, queue, length + 1); } static MOZ_MUST_USE bool InternalDequeue(JSContext* cx, HandleArrayObject queue, MutableHandleValue val) { uint32_t length; if (!GetLengthProperty(cx, queue, &length)) return false; MOZ_ASSERT(length != 0, "Queue should not be empty here"); if (!GetElement(cx, queue, queue, 0, val)) return false; uint32_t newlength = length - 1; RootedValue tmp(cx); for (uint32_t i = 0; i < newlength; i++) { if (!GetElement(cx, queue, queue, i + 1, &tmp)) return false; if (!DefineElement(cx, queue, i, tmp)) return false; } ObjectOpResult result; if (!DeleteElement(cx, queue, newlength, result)) return false; if (!result) { RootedId id(cx, INT_TO_JSID(newlength)); return result.reportError(cx, queue, id); } return SetLengthProperty(cx, queue, newlength); } /* static */ MOZ_MUST_USE bool AsyncGeneratorObject::enqueueRequest(JSContext* cx, Handle asyncGenObj, Handle request) { if (asyncGenObj->isSingleQueue()) { if (asyncGenObj->isSingleQueueEmpty()) { asyncGenObj->setSingleQueueRequest(request); return true; } RootedArrayObject queue(cx, NewDenseEmptyArray(cx)); if (!queue) return false; if (!NewbornArrayPush(cx, queue, ObjectValue(*asyncGenObj->singleQueueRequest()))) return false; if (!NewbornArrayPush(cx, queue, ObjectValue(*request))) return false; asyncGenObj->setQueue(queue); return true; } RootedArrayObject queue(cx, asyncGenObj->queue()); RootedValue requestVal(cx, ObjectValue(*request)); return InternalEnqueue(cx, queue, requestVal); } /* static */ AsyncGeneratorRequest* AsyncGeneratorObject::dequeueRequest(JSContext* cx, Handle asyncGenObj) { if (asyncGenObj->isSingleQueue()) { AsyncGeneratorRequest* request = asyncGenObj->singleQueueRequest(); asyncGenObj->clearSingleQueueRequest(); return request; } RootedArrayObject queue(cx, asyncGenObj->queue()); RootedValue requestVal(cx); if (!InternalDequeue(cx, queue, &requestVal)) return nullptr; return &requestVal.toObject().as(); } /* static */ AsyncGeneratorRequest* AsyncGeneratorObject::peekRequest(JSContext* cx, Handle asyncGenObj) { if (asyncGenObj->isSingleQueue()) return asyncGenObj->singleQueueRequest(); RootedArrayObject queue(cx, asyncGenObj->queue()); RootedValue requestVal(cx); if (!GetElement(cx, queue, queue, 0, &requestVal)) return nullptr; return &requestVal.toObject().as(); } const Class AsyncGeneratorRequest::class_ = { "AsyncGeneratorRequest", JSCLASS_HAS_RESERVED_SLOTS(AsyncGeneratorRequest::Slots) }; // Async Iteration proposal 11.4.3.1. /* static */ AsyncGeneratorRequest* AsyncGeneratorRequest::create(JSContext* cx, CompletionKind completionKind_, HandleValue completionValue_, HandleObject promise_) { RootedObject obj(cx, NewNativeObjectWithGivenProto(cx, &class_, nullptr)); if (!obj) return nullptr; Handle request = obj.as(); request->setCompletionKind(completionKind_); request->setCompletionValue(completionValue_); request->setPromise(promise_); return request; } // Async Iteration proposal 11.4.3.2 AsyncGeneratorStart steps 5.d-g. static MOZ_MUST_USE bool AsyncGeneratorReturned(JSContext* cx, Handle asyncGenObj, HandleValue value) { // Step 5.d. asyncGenObj->setCompleted(); // Step 5.e (done in bytecode). // Step 5.f.i (implicit). // Step 5.g. return AsyncGeneratorResolve(cx, asyncGenObj, value, true); } // Async Iteration proposal 11.4.3.2 AsyncGeneratorStart steps 5.d, f. static MOZ_MUST_USE bool AsyncGeneratorThrown(JSContext* cx, Handle asyncGenObj) { // Step 5.d. asyncGenObj->setCompleted(); // Not much we can do about uncatchable exceptions, so just bail. if (!cx->isExceptionPending()) return false; // Step 5.f.i. RootedValue value(cx); if (!GetAndClearException(cx, &value)) return false; // Step 5.f.ii. return AsyncGeneratorReject(cx, asyncGenObj, value); } // Async Iteration proposal 11.4.3.7 (partially). // Most steps are done in generator. static MOZ_MUST_USE bool AsyncGeneratorYield(JSContext* cx, Handle asyncGenObj, HandleValue value) { // Step 5 is done in bytecode. // Step 6. asyncGenObj->setSuspendedYield(); // Step 9. return AsyncGeneratorResolve(cx, asyncGenObj, value, false); } // Async Iteration proposal 4.1 Await steps 2-9. // Async Iteration proposal 8.2.1 yield* steps 6.a.vii, 6.b.ii.7, 6.c.ix. // Async Iteration proposal 11.4.3.2 AsyncGeneratorStart step 5.f-g. // Async Iteration proposal 11.4.3.5 AsyncGeneratorResumeNext // steps 12-14, 16-20. // Execution context switching is handled in generator. MOZ_MUST_USE bool js::AsyncGeneratorResume(JSContext* cx, Handle asyncGenObj, CompletionKind completionKind, HandleValue argument) { RootedValue generatorVal(cx, asyncGenObj->generatorVal()); // 11.4.3.5 steps 12-14, 16-20. HandlePropertyName funName = completionKind == CompletionKind::Normal ? cx->names().StarGeneratorNext : completionKind == CompletionKind::Throw ? cx->names().StarGeneratorThrow : cx->names().StarGeneratorReturn; FixedInvokeArgs<1> args(cx); args[0].set(argument); RootedValue result(cx); if (!CallSelfHostedFunction(cx, funName, generatorVal, args, &result)) { // 11.4.3.2 step 5.d, f. return AsyncGeneratorThrown(cx, asyncGenObj); } // 4.1 steps 2-9. if (asyncGenObj->generatorObj()->isAfterAwait()) return AsyncGeneratorAwait(cx, asyncGenObj, result); // The following code corresponds to the following 3 cases: // * yield // * yield* // * return // For yield and return, property access is done on an internal result // object and it's not observable. // For yield*, it's done on a possibly user-provided result object, and // it's observable. // // Note that IteratorComplete steps in 8.2.1 are done in bytecode. // 8.2.1 yield* steps 6.a.vii, 6.b.ii.7, 6.c.ix. RootedObject resultObj(cx, &result.toObject()); RootedValue value(cx); if (!GetProperty(cx, resultObj, resultObj, cx->names().value, &value)) return false; if (asyncGenObj->generatorObj()->isAfterYield()) return AsyncGeneratorYield(cx, asyncGenObj, value); // 11.4.3.2 step 5.d-g. return AsyncGeneratorReturned(cx, asyncGenObj, value); } static const JSFunctionSpec async_iterator_proto_methods[] = { JS_SELF_HOSTED_SYM_FN(asyncIterator, "AsyncIteratorIdentity", 0, 0), JS_FS_END }; static const JSFunctionSpec async_from_sync_iter_methods[] = { JS_FN("next", AsyncFromSyncIteratorNext, 1, 0), JS_FN("throw", AsyncFromSyncIteratorThrow, 1, 0), JS_FN("return", AsyncFromSyncIteratorReturn, 1, 0), JS_FS_END }; static const JSFunctionSpec async_generator_methods[] = { JS_FN("next", AsyncGeneratorNext, 1, 0), JS_FN("throw", AsyncGeneratorThrow, 1, 0), JS_FN("return", AsyncGeneratorReturn, 1, 0), JS_FS_END }; /* static */ MOZ_MUST_USE bool GlobalObject::initAsyncGenerators(JSContext* cx, Handle global) { if (global->getReservedSlot(ASYNC_ITERATOR_PROTO).isObject()) return true; // Async Iteration proposal 11.1.2 %AsyncIteratorPrototype%. RootedObject asyncIterProto(cx, GlobalObject::createBlankPrototype(cx, global)); if (!asyncIterProto) return false; if (!DefinePropertiesAndFunctions(cx, asyncIterProto, nullptr, async_iterator_proto_methods)) return false; // Async Iteration proposal 11.1.3.2 %AsyncFromSyncIteratorPrototype%. RootedObject asyncFromSyncIterProto( cx, GlobalObject::createBlankPrototypeInheriting(cx, global, &PlainObject::class_, asyncIterProto)); if (!asyncFromSyncIterProto) return false; if (!DefinePropertiesAndFunctions(cx, asyncFromSyncIterProto, nullptr, async_from_sync_iter_methods) || !DefineToStringTag(cx, asyncFromSyncIterProto, cx->names().AsyncFromSyncIterator)) { return false; } // Async Iteration proposal 11.4.1 %AsyncGeneratorPrototype%. RootedObject asyncGenProto( cx, GlobalObject::createBlankPrototypeInheriting(cx, global, &PlainObject::class_, asyncIterProto)); if (!asyncGenProto) return false; if (!DefinePropertiesAndFunctions(cx, asyncGenProto, nullptr, async_generator_methods) || !DefineToStringTag(cx, asyncGenProto, cx->names().AsyncGenerator)) { return false; } // Async Iteration proposal 11.3.3 %AsyncGenerator%. RootedObject asyncGenerator(cx, NewSingletonObjectWithFunctionPrototype(cx, global)); if (!asyncGenerator) return false; if (!JSObject::setDelegate(cx, asyncGenerator)) return false; if (!LinkConstructorAndPrototype(cx, asyncGenerator, asyncGenProto, JSPROP_READONLY, JSPROP_READONLY) || !DefineToStringTag(cx, asyncGenerator, cx->names().AsyncGeneratorFunction)) { return false; } RootedValue function(cx, global->getConstructor(JSProto_Function)); if (!function.toObjectOrNull()) return false; RootedObject proto(cx, &function.toObject()); RootedAtom name(cx, cx->names().AsyncGeneratorFunction); // Async Iteration proposal 11.3.2 %AsyncGeneratorFunction%. RootedObject asyncGenFunction( cx, NewFunctionWithProto(cx, AsyncGeneratorConstructor, 1, JSFunction::NATIVE_CTOR, nullptr, name, proto, gc::AllocKind::FUNCTION, SingletonObject)); if (!asyncGenFunction) return false; if (!LinkConstructorAndPrototype(cx, asyncGenFunction, asyncGenerator, JSPROP_PERMANENT | JSPROP_READONLY, JSPROP_READONLY)) { return false; } global->setReservedSlot(ASYNC_ITERATOR_PROTO, ObjectValue(*asyncIterProto)); global->setReservedSlot(ASYNC_FROM_SYNC_ITERATOR_PROTO, ObjectValue(*asyncFromSyncIterProto)); global->setReservedSlot(ASYNC_GENERATOR, ObjectValue(*asyncGenerator)); global->setReservedSlot(ASYNC_GENERATOR_FUNCTION, ObjectValue(*asyncGenFunction)); global->setReservedSlot(ASYNC_GENERATOR_PROTO, ObjectValue(*asyncGenProto)); return true; }