From 0e5746aaf02528fc988bf6771958678980f341e1 Mon Sep 17 00:00:00 2001 From: Cameron Kaiser Date: Sun, 18 Aug 2019 14:26:54 -0700 Subject: [PATCH] #521: baseline parser support for async/await, with toggle, without bytecode (passes tests) --- js/src/builtin/AsyncFunctions.js | 51 +++ js/src/builtin/Promise.cpp | 142 ++++++ js/src/builtin/Promise.h | 29 ++ js/src/builtin/Promise.js | 253 ++++++++++ js/src/builtin/ReflectParse.cpp | 30 +- js/src/builtin/SelfHostingDefines.h | 9 + js/src/frontend/BytecodeEmitter.cpp | 7 + js/src/frontend/BytecodeEmitter.h | 2 + js/src/frontend/FoldConstants.cpp | 2 + js/src/frontend/FullParseHandler.h | 5 + js/src/frontend/NameFunctions.cpp | 1 + js/src/frontend/ParseNode.cpp | 3 +- js/src/frontend/ParseNode.h | 4 +- js/src/frontend/Parser.cpp | 431 +++++++++++++++--- js/src/frontend/Parser.h | 27 +- js/src/frontend/SyntaxParseHandler.h | 1 + js/src/jit/CodeGenerator.cpp | 2 +- js/src/js.msg | 11 + js/src/jsapi.h | 8 + js/src/jscompartment.cpp | 1 + js/src/jscompartment.h | 1 + js/src/jsfun.cpp | 12 +- js/src/jsprototypes.h | 1 + js/src/jsscript.cpp | 1 + js/src/moz.build | 5 + js/src/shell/js.cpp | 77 ++++ .../tests/ecma_7/AsyncFunctions/104_basic.js | 38 ++ .../tests/ecma_7/AsyncFunctions/property.js | 49 ++ js/src/tests/ecma_7/AsyncFunctions/shell.js | 0 js/src/tests/js1_5/GC/regress-319980-01.js | 14 +- js/src/tests/js1_5/Regress/regress-314401.js | 16 +- js/src/vm/CommonPropertyNames.h | 1 + js/src/vm/GlobalObject.cpp | 1 + js/src/vm/GlobalObject.h | 7 + js/src/vm/ScopeObject.cpp | 7 +- js/src/vm/SelfHosting.cpp | 70 +++ js/src/vm/Xdr.h | 4 +- js/xpconnect/src/XPCJSRuntime.cpp | 3 + modules/libpref/init/all.js | 1 + 39 files changed, 1236 insertions(+), 91 deletions(-) create mode 100644 js/src/builtin/AsyncFunctions.js create mode 100644 js/src/builtin/Promise.cpp create mode 100644 js/src/builtin/Promise.h create mode 100644 js/src/builtin/Promise.js create mode 100644 js/src/tests/ecma_7/AsyncFunctions/104_basic.js create mode 100644 js/src/tests/ecma_7/AsyncFunctions/property.js create mode 100644 js/src/tests/ecma_7/AsyncFunctions/shell.js diff --git a/js/src/builtin/AsyncFunctions.js b/js/src/builtin/AsyncFunctions.js new file mode 100644 index 0000000000..2fd71099c2 --- /dev/null +++ b/js/src/builtin/AsyncFunctions.js @@ -0,0 +1,51 @@ +/* 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/. */ + +function AsyncFunction_wrap(genFunction) { + var wrapper = function() { + // The try block is required to handle throws in default arguments properly. + try { + return AsyncFunction_start(callFunction(std_Function_apply, genFunction, this, arguments)); + } catch (e) { + return Promise_reject(e); + } + }; + /* not yet + std_Object_setPrototypeOf(wrapper, GetBuiltinPrototype("AsyncFunction")); + */ + _DefineDataProperty(wrapper, "length", genFunction.length); + SetFunctionExtendedSlot(genFunction, 1, wrapper); + var name = GetFunName(genFunction); + if (typeof name !== 'undefined') + SetFunName(wrapper, name); + return wrapper; +} + +function AsyncFunction_start(generator) { + return AsyncFunction_resume(generator, undefined, generator.next); +} + +function AsyncFunction_resume(gen, v, method) { + let result; + try { + // get back into async function, run to next await point + result = callFunction(method, gen, v); + } catch (exc) { + // The async function itself failed. + return Promise_reject(exc); + } + + if (result.done) + return Promise_resolve(result.value); + + // If we get here, `await` occurred. `gen` is paused at a yield point. + return callFunction(Promise_then, + Promise_resolve(result.value), + function(val) { + return AsyncFunction_resume(gen, val, gen.next); + }, function (err) { + return AsyncFunction_resume(gen, err, gen.throw); + }); +} + diff --git a/js/src/builtin/Promise.cpp b/js/src/builtin/Promise.cpp new file mode 100644 index 0000000000..080d7f65d3 --- /dev/null +++ b/js/src/builtin/Promise.cpp @@ -0,0 +1,142 @@ +/* -*- 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 "builtin/Promise.h" + +#include "jscntxt.h" + +#include "builtin/SelfHostingDefines.h" + +#include "jsobjinlines.h" + +using namespace js; + +static const JSFunctionSpec promise_methods[] = { + JS_SELF_HOSTED_FN("catch", "Promise_catch", 1, 0), + JS_SELF_HOSTED_FN("then", "Promise_then", 2, 0), + JS_FS_END +}; + +static const JSFunctionSpec promise_static_methods[] = { + JS_SELF_HOSTED_FN("all", "Promise_all", 1, 0), + JS_SELF_HOSTED_FN("race", "Promise_race", 1, 0), + JS_SELF_HOSTED_FN("reject", "Promise_reject", 1, 0), + JS_SELF_HOSTED_FN("resolve", "Promise_resolve", 1, 0), + JS_FS_END +}; + +namespace js { + +bool +PromiseConstructor(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + if (args.length() == 0) { + JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED, + "ShellPromise.constructor", "0", "s"); + return false; + } + + HandleValue fn = args.get(0); + + if (!IsCallable(fn)) { + JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_NOT_CALLABLE, + "Argument 1 of ShellPromise.constructor"); + return false; + } + + RootedObject promise(cx, NewBuiltinClassInstance(cx, &ShellPromiseObject::class_)); + if (!promise) + return false; + + JS_SetReservedSlot(promise, PROMISE_STATE_SLOT, NumberValue(PROMISE_STATE_PENDING)); + JS_SetReservedSlot(promise, PROMISE_VALUE_SLOT, NullValue()); + JS_SetReservedSlot(promise, PROMISE_DEFERREDS_SLOT, NullValue()); + + RootedValue initRval(cx); + JS::AutoValueVector argValues(cx); + argValues.append(ObjectValue(*promise)); + argValues.append(fn); + HandleValueArray arr(argValues); + + JSAtom* promiseInitAtom; + if (!(promiseInitAtom = Atomize(cx, "Promise_init", 12))) + return false; + + RootedPropertyName name(cx, promiseInitAtom->asPropertyName()); + RootedValue selfHostedFun(cx); + + if (!GlobalObject::getIntrinsicValue(cx, cx->global(), name, &selfHostedFun)) + return false; + + if (!JS_CallFunctionValue(cx, promise, selfHostedFun, arr, &initRval)) + return false; + + args.rval().setObject(*promise); + + return true; +} + +} + +static JSObject* +CreatePromisePrototype(JSContext* cx, JSProtoKey key) +{ + return cx->global()->createBlankPrototype(cx, &ShellPromiseObject::protoClass_); +} + +const Class ShellPromiseObject::class_ = { + "ShellPromise", + JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS) | JSCLASS_HAS_CACHED_PROTO(JSProto_ShellPromise), + nullptr, /* addProperty */ + nullptr, /* delProperty */ + nullptr, /* getProperty */ + nullptr, /* setProperty */ + nullptr, /* enumerate */ + nullptr, /* resolve */ + nullptr, /* mayResolve */ + nullptr, /* finalize */ + nullptr, /* call */ + nullptr, /* hasInstance */ + nullptr, /* construct */ + nullptr, /* trace */ + { + GenericCreateConstructor, + CreatePromisePrototype, + promise_static_methods, + nullptr, + promise_methods + } +}; + +const Class ShellPromiseObject::protoClass_ = { + "ShellPromise", + JSCLASS_HAS_CACHED_PROTO(JSProto_ShellPromise), + nullptr, /* addProperty */ + nullptr, /* delProperty */ + nullptr, /* getProperty */ + nullptr, /* setProperty */ + nullptr, /* enumerate */ + nullptr, /* resolve */ + nullptr, /* mayResolve */ + nullptr, /* finalize */ + nullptr, /* call */ + nullptr, /* hasInstance */ + nullptr, /* construct */ + nullptr, /* trace */ + { + DELEGATED_CLASSSPEC(&ShellPromiseObject::class_.spec), + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + ClassSpec::IsDelegated + } +}; + diff --git a/js/src/builtin/Promise.h b/js/src/builtin/Promise.h new file mode 100644 index 0000000000..53f1399416 --- /dev/null +++ b/js/src/builtin/Promise.h @@ -0,0 +1,29 @@ +/* -*- 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/. */ + +#ifndef builtin_Promise_h +#define builtin_Promise_h + +#include "vm/NativeObject.h" + +namespace js { + +class AutoSetNewObjectMetadata; + +class ShellPromiseObject : public NativeObject +{ + public: + static const unsigned RESERVED_SLOTS = 3; + static const Class class_; + static const Class protoClass_; +}; + +bool +PromiseConstructor(JSContext* cx, unsigned argc, Value* vp); + +} // namespace js + +#endif /* builtin_Promise_h */ diff --git a/js/src/builtin/Promise.js b/js/src/builtin/Promise.js new file mode 100644 index 0000000000..a0416056eb --- /dev/null +++ b/js/src/builtin/Promise.js @@ -0,0 +1,253 @@ +/* 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/. */ + +/** + * This implementation is by no means complete and is using a polyfill. + * In particular, it doesn't fully comply to ES6 Promises spec. + * The list of incompatibilities may not be complete and includes: + * + * - no [Symbol.species] implementation + * - implementation is not really async at all, but all is executed + * in correct order + * - Promise.race is not implemented (not necessary for async/await) + * - Promise.all implementation currently only handles arrays, no other + * iterables. + */ + + /** + * This polyfill implements the Promise specification in the following way: + * At first, Promise is initialized with the intrinsic Promise constructor, + * with its initialization completed by calling Promise_init. The goal is to + * completely resolve/reject the promise. There are certain helper functions: + * - resolveOneStep() executes "one step" of the promise - which means + * getting either the final value or next promise towards complete execution. + * - resolveCompletely() executes the entire promise, which merely means + * resolving one step at a time, until the final step no longer resolves + * to a promise (which means either resolving to a value or rejecting). + * + * Once resolution is finished, resolveCompletely() is called in order to + * update state of the promise. It also spawns callbacks that may have been + * deferred with a then() - they are NOT normally taken into consideration, + * because resolveCompletely() just runs one path. + */ + +/** + * Below is a simple simulation of an event loop. Events enqueued using + * this setTimeout implementation must be manually triggered by calling + * runEvents(). + */ +var eventQueue = new List(); + +function runEvents() { + while (eventQueue.length > 0) { + var evt = callFunction(std_Array_pop, eventQueue); + evt(); + } +} + +function setTimeout(cb, interval) { + if (!IsCallable(cb)) + ThrowTypeError(JSMSG_NOT_CALLABLE, "Argument 0"); + if (interval !== 0) + ThrowTypeError(JSMSG_SETTIMEOUT_INTERVAL_NONZERO); + callFunction(std_Array_push, eventQueue, cb); +} + +function Handler(onFulfilled, onRejected, resolve, reject) { + this.onFulfilled = IsCallable(onFulfilled) ? onFulfilled : null; + this.onRejected = IsCallable(onRejected) ? onRejected : null; + this.resolve = resolve; + this.reject = reject; +} + +MakeConstructible(Handler, std_Object_create(null)); + +function Promise_setState(promise, state) { + UnsafeSetReservedSlot(promise, PROMISE_STATE_SLOT, state); +} + +function Promise_getState(promise) { + return UnsafeGetReservedSlot(promise, PROMISE_STATE_SLOT); +} + +function Promise_setDeferreds(promise, deferreds) { + UnsafeSetReservedSlot(promise, PROMISE_DEFERREDS_SLOT, deferreds); +} + +function Promise_getDeferreds(promise) { + return UnsafeGetReservedSlot(promise, PROMISE_DEFERREDS_SLOT); +} + +function Promise_setValue(promise, value) { + UnsafeSetReservedSlot(promise, PROMISE_VALUE_SLOT, value); +} + +function Promise_getValue(promise) { + return UnsafeGetReservedSlot(promise, PROMISE_VALUE_SLOT); +} + +function Promise_init(promise, fn) { + Promise_setDeferreds(promise, new List()); + resolveOneStep(fn, + function(value) { resolveCompletely(promise, value); }, + function(reason) { reject(promise, reason); }); +} + +function Promise_isThenable(valueOrPromise) { + if (valueOrPromise && (typeof valueOrPromise === 'object' || IsCallable(valueOrPromise))) { + var then = valueOrPromise.then; + if (IsCallable(then)) + return true; + } + return false; +} + +function Promise_all(promises) { + var length = promises.length; + var results = []; + return NewPromise(function (resolve, reject) { + if (length === 0) + return resolve([]); + var remaining = length; + function resolveChain(index, valueOrPromise) { + try { + if (Promise_isThenable(valueOrPromise)) { + callFunction(valueOrPromise.then, valueOrPromise, + function (valueOrPromise) { resolveChain(index, valueOrPromise); }, + reject); + } else { + _DefineDataProperty(results, index, valueOrPromise); + if (--remaining === 0) + resolve(results); + } + } catch (ex) { + reject(ex); + } + } + for (var i = 0; i < length; i++) + resolveChain(i, promises[i]); + }); +} + +function Promise_race(values) { + ThrowTypeError(JSMSG_NOT_IMPLEMENTED, "Promise.race"); +} + +function Promise_reject(value) { + return NewPromise(function (resolve, reject) { + reject(value); + }); +} + +function Promise_resolve(value) { + if (value && typeof value === 'object' && IsPromise(value)) + return value; + + return NewPromise(function (resolve) { + resolve(value); + }); +} + +function Promise_catch(onRejected) { + return callFunction(Promise_then, this, undefined, onRejected); +} + +function asap(cb) { + setTimeout(cb, 0); +} + +function deferOrExecute(promise, deferred) { + if (Promise_getState(promise) === PROMISE_STATE_PENDING) { + /* work around "self-hosted code may not contain direct method calls" + Promise_getDeferreds(promise).push(deferred); */ + callFunction(std_Array_push, Promise_getDeferreds(promise), deferred); + return; + } + + asap(function() { + var cb = Promise_getState(promise) === PROMISE_STATE_RESOLVED ? + deferred.onFulfilled : deferred.onRejected; + if (cb === null) { + var value = Promise_getValue(promise); + /* (Promise_getState(promise) === PROMISE_STATE_RESOLVED ? deferred.resolve : deferred.reject)(value); */ + if (Promise_getState(promise) == PROMISE_STATE_RESOLVED) { + callFunction(deferred.resolve, deferred, value); + } else { + callFunction(deferred.reject, deferred, value); + } + return; + } + var returnValue; + try { + returnValue = cb(Promise_getValue(promise)); + } catch (e) { + /* sigh: deferred.reject(e); */ + callFunction(deferred.reject, deferred, e); + return; + } + /* deferred.resolve(returnValue); */ + callFunction(deferred.resolve, deferred, returnValue); + }); +} + +function resolveOneStep(fn, onFulfilled, onRejected) { + var done = false; + var callOnce = function(cb) { + return function(value) { + if (done) return; + done = true; + cb(value); + }; + }; + + try { + fn(callOnce(onFulfilled), callOnce(onRejected)); + } catch (ex) { + callOnce(onRejected)(ex); + } +} + +function resolveCompletely(promise, valueOrPromise) { + try { + // FIXME this is probably not a type error + if (valueOrPromise === promise) + ThrowTypeError(JSMSG_PROMISE_RESOLVED_WITH_ITSELF); + + if (Promise_isThenable(valueOrPromise)) { + resolveOneStep(function(resolve, reject) { + /* valueOrPromise.then(resolve, reject); */ + callFunction(valueOrPromise.then, valueOrPromise, resolve, reject); + }, + function(value) { resolveCompletely(promise, value); }, + function(value) { reject(promise, value); }); + } + else + callFunction(resolvingFinished, promise, PROMISE_STATE_RESOLVED, valueOrPromise); + } catch (ex) { + callFunction(reject, promise, ex); + } +} + +function reject(promise, reason) { + callFunction(resolvingFinished, promise, PROMISE_STATE_REJECTED, reason); +} + +function resolvingFinished(state, newValue) { + Promise_setState(this, state); + Promise_setValue(this, newValue); + var deferreds = Promise_getDeferreds(this); + for (var i = 0, len = deferreds.length; i < len; i++) + deferOrExecute(this, deferreds[i]); + Promise_setDeferreds(this, null); +} + +function Promise_then(onFulfilled, onRejected) { + var promise = this; + var newPromise = NewPromise(function(resolve, reject) { + deferOrExecute(promise, + new Handler(onFulfilled, onRejected, resolve, reject)); + }); + runEvents(); + return newPromise; +} diff --git a/js/src/builtin/ReflectParse.cpp b/js/src/builtin/ReflectParse.cpp index e95c37df1a..7b58955f3a 100644 --- a/js/src/builtin/ReflectParse.cpp +++ b/js/src/builtin/ReflectParse.cpp @@ -93,6 +93,7 @@ enum UnaryOperator { UNOP_BITNOT, UNOP_TYPEOF, UNOP_VOID, + UNOP_AWAIT, UNOP_LIMIT }; @@ -162,7 +163,8 @@ static const char* const unopNames[] = { "!", /* UNOP_NOT */ "~", /* UNOP_BITNOT */ "typeof", /* UNOP_TYPEOF */ - "void" /* UNOP_VOID */ + "void", /* UNOP_VOID */ + "await" /* UNOP_AWAIT */ }; static const char* const nodeTypeNames[] = { @@ -466,7 +468,7 @@ class NodeBuilder bool function(ASTType type, TokenPos* pos, HandleValue id, NodeVector& args, NodeVector& defaults, HandleValue body, HandleValue rest, GeneratorStyle generatorStyle, - bool isExpression, MutableHandleValue dst); + bool isAsync, bool isExpression, MutableHandleValue dst); bool variableDeclarator(HandleValue id, HandleValue init, TokenPos* pos, MutableHandleValue dst); @@ -1602,7 +1604,7 @@ bool NodeBuilder::function(ASTType type, TokenPos* pos, HandleValue id, NodeVector& args, NodeVector& defaults, HandleValue body, HandleValue rest, - GeneratorStyle generatorStyle, bool isExpression, + GeneratorStyle generatorStyle, bool isAsync, bool isExpression, MutableHandleValue dst) { RootedValue array(cx), defarray(cx); @@ -1613,6 +1615,7 @@ NodeBuilder::function(ASTType type, TokenPos* pos, bool isGenerator = generatorStyle != GeneratorStyle::None; RootedValue isGeneratorVal(cx, BooleanValue(isGenerator)); + RootedValue isAsyncVal(cx, BooleanValue(isAsync)); RootedValue isExpressionVal(cx, BooleanValue(isExpression)); RootedValue cb(cx, callbacks[type]); @@ -1636,6 +1639,7 @@ NodeBuilder::function(ASTType type, TokenPos* pos, "body", body, "rest", rest, "generator", isGeneratorVal, + "async", isAsyncVal, "style", styleVal, "expression", isExpressionVal, dst); @@ -1648,6 +1652,7 @@ NodeBuilder::function(ASTType type, TokenPos* pos, "body", body, "rest", rest, "generator", isGeneratorVal, + "async", isAsyncVal, "expression", isExpressionVal, dst); } @@ -1818,6 +1823,7 @@ class ASTSerializer bool function(ParseNode* pn, ASTType type, MutableHandleValue dst); bool functionArgsAndBody(ParseNode* pn, NodeVector& args, NodeVector& defaults, + bool isAsync, bool isExpression, MutableHandleValue body, MutableHandleValue rest); bool functionBody(ParseNode* pn, TokenPos* pos, MutableHandleValue dst); @@ -1893,6 +1899,9 @@ ASTSerializer::unop(ParseNodeKind kind, JSOp op) if (IsTypeofKind(kind)) return UNOP_TYPEOF; + if (kind == PNK_AWAIT) + return UNOP_AWAIT; + switch (op) { case JSOP_NEG: return UNOP_NEG; @@ -2974,6 +2983,7 @@ ASTSerializer::expression(ParseNode* pn, MutableHandleValue dst) case PNK_NOT: case PNK_BITNOT: case PNK_POS: + case PNK_AWAIT: case PNK_NEG: { MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_kid->pn_pos)); @@ -3464,6 +3474,7 @@ ASTSerializer::function(ParseNode* pn, ASTType type, MutableHandleValue dst) : GeneratorStyle::ES6) : GeneratorStyle::None; + bool isAsync = pn->pn_funbox->isAsync(); bool isExpression = #if JS_HAS_EXPR_CLOSURES func->isExprBody(); @@ -3484,13 +3495,14 @@ ASTSerializer::function(ParseNode* pn, ASTType type, MutableHandleValue dst) rest.setUndefined(); else rest.setNull(); - return functionArgsAndBody(pn->pn_body, args, defaults, &body, &rest) && + return functionArgsAndBody(pn->pn_body, args, defaults, isAsync, isExpression, &body, &rest) && builder.function(type, &pn->pn_pos, id, args, defaults, body, - rest, generatorStyle, isExpression, dst); + rest, generatorStyle, isAsync, isExpression, dst); } bool ASTSerializer::functionArgsAndBody(ParseNode* pn, NodeVector& args, NodeVector& defaults, + bool isAsync, bool isExpression, MutableHandleValue body, MutableHandleValue rest) { ParseNode* pnargs; @@ -3521,6 +3533,14 @@ ASTSerializer::functionArgsAndBody(ParseNode* pn, NodeVector& args, NodeVector& pnstart = pnstart->pn_next; } + // Async arrow with expression body is converted into STATEMENTLIST + // to insert initial yield. + if (isAsync && isExpression) { + MOZ_ASSERT(pnstart->getKind() == PNK_RETURN); + return functionArgs(pn, pnargs, pnbody, args, defaults, rest) && + expression(pnstart->pn_kid, body); + } + return functionArgs(pn, pnargs, pnbody, args, defaults, rest) && functionBody(pnstart, &pnbody->pn_pos, body); } diff --git a/js/src/builtin/SelfHostingDefines.h b/js/src/builtin/SelfHostingDefines.h index d3c4afb89f..98c61a747d 100644 --- a/js/src/builtin/SelfHostingDefines.h +++ b/js/src/builtin/SelfHostingDefines.h @@ -42,6 +42,15 @@ // Stores the private WeakMap slot used for WeakSets #define WEAKSET_MAP_SLOT 0 +// Slots for use with promises implementation. +#define PROMISE_STATE_SLOT 0 +#define PROMISE_VALUE_SLOT 1 +#define PROMISE_DEFERREDS_SLOT 2 + +#define PROMISE_STATE_PENDING 0 +#define PROMISE_STATE_RESOLVED 1 +#define PROMISE_STATE_REJECTED 2 + #define ITERATOR_SLOT_TARGET 0 // Used for collection iterators. #define ITERATOR_SLOT_RANGE 1 diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index 998e8027e6..664b926d0a 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -2058,6 +2058,7 @@ BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer) case PNK_YIELD_STAR: case PNK_YIELD: + case PNK_AWAIT: MOZ_ASSERT(pn->isArity(PN_BINARY)); *answer = true; return true; @@ -6488,6 +6489,11 @@ BytecodeEmitter::emitFunction(ParseNode* pn, bool needsProto) return true; } +bool +BytecodeEmitter::emitAsyncWrapper(unsigned index, bool needsHomeObject) { + MOZ_CRASH("NYI"); +} + bool BytecodeEmitter::emitDo(ParseNode* pn) { @@ -8472,6 +8478,7 @@ BytecodeEmitter::emitTree(ParseNode* pn, EmitLineNumberNote emitLineNote) break; case PNK_YIELD: + case PNK_AWAIT: if (!emitYield(pn)) return false; break; diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index 6fb611eaa7..e381434d5e 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -514,6 +514,8 @@ struct BytecodeEmitter bool emitPropOp(ParseNode* pn, JSOp op); bool emitPropIncDec(ParseNode* pn); + bool emitAsyncWrapper(unsigned index, bool isMethod); + bool emitComputedPropertyName(ParseNode* computedPropName); // Emit bytecode to put operands for a JSOP_GETELEM/CALLELEM/SETELEM/DELELEM diff --git a/js/src/frontend/FoldConstants.cpp b/js/src/frontend/FoldConstants.cpp index bd2ffd1451..36f1ddefe4 100644 --- a/js/src/frontend/FoldConstants.cpp +++ b/js/src/frontend/FoldConstants.cpp @@ -330,6 +330,7 @@ ContainsHoistedDeclaration(ExclusiveContext* cx, ParseNode* node, bool* result) case PNK_CONDITIONAL: case PNK_TYPEOFNAME: case PNK_TYPEOFEXPR: + case PNK_AWAIT: case PNK_VOID: case PNK_NOT: case PNK_BITNOT: @@ -1855,6 +1856,7 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, Parser& parser, bo return Fold(cx, &pn->pn_left, parser, inGenexpLambda); case PNK_YIELD: + case PNK_AWAIT: MOZ_ASSERT(pn->isArity(PN_BINARY)); MOZ_ASSERT((pn->pn_right->isKind(PNK_NAME) && !pn->pn_right->isAssigned()) || (pn->pn_right->isKind(PNK_ASSIGN) && diff --git a/js/src/frontend/FullParseHandler.h b/js/src/frontend/FullParseHandler.h index 4670b64af3..e26d6fb47d 100644 --- a/js/src/frontend/FullParseHandler.h +++ b/js/src/frontend/FullParseHandler.h @@ -450,6 +450,11 @@ class FullParseHandler return new_(PNK_YIELD_STAR, JSOP_NOP, pos, value, gen); } + ParseNode* newAwaitExpression(uint32_t begin, ParseNode* value, ParseNode* gen) { + TokenPos pos(begin, value ? value->pn_pos.end : begin + 1); + return new_(PNK_AWAIT, JSOP_YIELD, pos, value, gen); + } + // Statements ParseNode* newStatementList(unsigned blockid, const TokenPos& pos) { diff --git a/js/src/frontend/NameFunctions.cpp b/js/src/frontend/NameFunctions.cpp index d9f97c1c52..f5c5eb686d 100644 --- a/js/src/frontend/NameFunctions.cpp +++ b/js/src/frontend/NameFunctions.cpp @@ -493,6 +493,7 @@ class NameResolver break; case PNK_YIELD: + case PNK_AWAIT: MOZ_ASSERT(cur->isArity(PN_BINARY)); if (cur->pn_left) { if (!resolve(cur->pn_left, prefix)) diff --git a/js/src/frontend/ParseNode.cpp b/js/src/frontend/ParseNode.cpp index f4fcb1ee3c..ed59abc97a 100644 --- a/js/src/frontend/ParseNode.cpp +++ b/js/src/frontend/ParseNode.cpp @@ -319,7 +319,8 @@ PushNodeChildren(ParseNode* pn, NodeStack* stack) // variable, or an assignment of a PNK_GENERATOR node to the '.generator' // local, for a synthesized, prepended initial yield. Yum! case PNK_YIELD_STAR: - case PNK_YIELD: { + case PNK_YIELD: + case PNK_AWAIT: { MOZ_ASSERT(pn->isArity(PN_BINARY)); MOZ_ASSERT(pn->pn_right); MOZ_ASSERT(pn->pn_right->isKind(PNK_NAME) || diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h index 67de5661d2..effb9de96d 100644 --- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -185,6 +185,7 @@ class PackedScopeCoordinate F(VOID) \ F(NOT) \ F(BITNOT) \ + F(AWAIT) \ \ /* \ * Binary operators. \ @@ -425,7 +426,8 @@ IsTypeofKind(ParseNodeKind kind) * PNK_NEG * PNK_VOID, unary pn_kid: UNARY expr * PNK_NOT, - * PNK_BITNOT + * PNK_BITNOT, + * PNK_AWAIT * PNK_TYPEOFNAME, unary pn_kid: UNARY expr * PNK_TYPEOFEXPR * PNK_PREINCREMENT, unary pn_kid: MEMBER expr diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index 78852eff77..453ddea83e 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -958,7 +958,8 @@ Parser::checkStrictBinding(PropertyName* name, Node pn) if (!pc->sc->needStrictChecks()) return true; - if (name == context->names().eval || name == context->names().arguments || IsKeyword(name)) { + if (name == context->names().eval || name == context->names().arguments || + (IsKeyword(name) && name != context->names().await)) { JSAutoByteString bytes; if (!AtomToPrintableString(context, name, &bytes)) return false; @@ -969,6 +970,7 @@ Parser::checkStrictBinding(PropertyName* name, Node pn) return true; } +/* ::moduleBody */ template typename ParseHandler::Node Parser::standaloneModule(HandleModuleObject module) @@ -979,6 +981,7 @@ Parser::standaloneModule(HandleModuleObject module) if (!mn) return null(); + AutoAwaitIsKeyword awaitIsKeyword(&tokenStream, true); ModuleBox* modulebox = newModuleBox(mn, module); if (!modulebox) return null(); @@ -1139,6 +1142,16 @@ Parser::defineFunctionThis() return true; } +static YieldHandling +GetYieldHandling(GeneratorKind generatorKind, FunctionAsyncKind asyncKind) +{ + if (asyncKind == AsyncFunction) + return YieldIsName; + if (generatorKind == NotGenerator) + return YieldIsName; + return YieldIsKeyword; +} + template <> ParseNode* Parser::standaloneFunctionBody(HandleFunction fun, @@ -1176,7 +1189,7 @@ Parser::standaloneFunctionBody(HandleFunction fun, return null(); } - YieldHandling yieldHandling = generatorKind != NotGenerator ? YieldIsKeyword : YieldIsName; + YieldHandling yieldHandling = GetYieldHandling(generatorKind, asyncKind); ParseNode* pn = functionBody(InAllowed, yieldHandling, Statement, StatementListBody); if (!pn) return null(); @@ -1341,6 +1354,16 @@ Parser::functionBody(InHandling inHandling, YieldHandling yieldHan } else { MOZ_ASSERT(type == ExpressionBody); + // Async functions are implemented as star generators, and star + // generators are assumed to be statement lists, to prepend initial + // `yield`. + Node stmtList = null(); + if (pc->isAsync()) { + stmtList = handler.newStatementList(pc->blockid(), pos()); + if (!stmtList) + return null(); + } + Node kid = assignExpr(inHandling, yieldHandling, TripledotProhibited); if (!kid) return null(); @@ -1348,6 +1371,11 @@ Parser::functionBody(InHandling inHandling, YieldHandling yieldHan pn = handler.newReturnStatement(kid, handler.getPosition(kid)); if (!pn) return null(); + + if (pc->isAsync()) { + handler.addStatementToList(stmtList, pn, pc); + pn = stmtList; + } } switch (pc->generatorKind()) { @@ -1372,13 +1400,13 @@ Parser::functionBody(InHandling inHandling, YieldHandling yieldHan break; case StarGenerator: - MOZ_ASSERT(kind != Arrow); - MOZ_ASSERT(type == StatementListBody); + MOZ_ASSERT_IF(!pc->isAsync(), kind != Arrow); + MOZ_ASSERT_IF(!pc->isAsync(), type == StatementListBody); break; } if (pc->isGenerator()) { - MOZ_ASSERT(type == StatementListBody); + MOZ_ASSERT_IF(!pc->isAsync(), type == StatementListBody); Node generator = newName(context->names().dotGenerator); if (!generator) return null(); @@ -2060,19 +2088,47 @@ Parser::functionArguments(YieldHandling yieldHandling, FunctionSyn *hasRest = false; bool parenFreeArrow = false; - TokenStream::Modifier modifier = TokenStream::None; + // Modifier for the following tokens. + // TokenStream::None for the following cases: + // async a => 1 + // ^ + // + // (a) => 1 + // ^ + // + // async (a) => 1 + // ^ + // + // function f(a) {} + // ^ + // + // TokenStream::Operand for the following case: + // a => 1 + // ^ + TokenStream::Modifier firstTokenModifier = TokenStream::None; + + // Modifier for the the first token in each argument. + // can be changed to TokenStream::None for the following case: + // async a => 1 + // ^ + TokenStream::Modifier argModifier = TokenStream::Operand; if (kind == Arrow) { TokenKind tt; - if (!tokenStream.peekToken(&tt, TokenStream::Operand)) + // In async function, the first token after `async` is already gotten + // with TokenStream::None. + // In sync function, the first token is already gotten with + // TokenStream::Operand. + firstTokenModifier = funbox->isAsync() ? TokenStream::None : TokenStream::Operand; + if (!tokenStream.peekToken(&tt, firstTokenModifier)) return false; - if (tt == TOK_NAME) + if (tt == TOK_NAME || tt == TOK_YIELD) { // from bug 1305566 -r 347016 parenFreeArrow = true; - else - modifier = TokenStream::Operand; + argModifier = firstTokenModifier; + } } if (!parenFreeArrow) { TokenKind tt; - if (!tokenStream.getToken(&tt, modifier)) + if (!tokenStream.getToken(&tt, firstTokenModifier)) return false; if (tt != TOK_LP) { report(ParseError, false, null(), @@ -2117,9 +2173,11 @@ Parser::functionArguments(YieldHandling yieldHandling, FunctionSyn } TokenKind tt; - if (!tokenStream.getToken(&tt, TokenStream::Operand)) + if (!tokenStream.getToken(&tt, argModifier)) return false; - MOZ_ASSERT_IF(parenFreeArrow, tt == TOK_NAME); + argModifier = TokenStream::Operand; + // from bug 1305566 -r 347016 + MOZ_ASSERT_IF(parenFreeArrow, tt == TOK_NAME || tt == TOK_YIELD); switch (tt) { case TOK_LB: case TOK_LC: @@ -2141,8 +2199,7 @@ Parser::functionArguments(YieldHandling yieldHandling, FunctionSyn */ BindData data(context); data.initDestructuring(JSOP_DEFVAR); - Node destruct = destructuringExprWithoutYield(yieldHandling, &data, tt, - JSMSG_YIELD_IN_DEFAULT); + Node destruct = destructuringExprWithoutYieldOrAwait(yieldHandling, &data, tt); if (!destruct) return false; @@ -2198,12 +2255,21 @@ Parser::functionArguments(YieldHandling yieldHandling, FunctionSyn goto TOK_NAME; } - TOK_NAME: + TOK_NAME: /* from TOK_YIELD above */ case TOK_NAME: { if (parenFreeArrow) funbox->setStart(tokenStream); + if (funbox->isAsync() && tokenStream.currentName() == context->names().await) { + // `await` is already gotten as TOK_NAME for the following + // case: + // + // async await => 1 + report(ParseError, false, null(), JSMSG_RESERVED_ID, "await"); + return false; + } + RootedPropertyName name(context, tokenStream.currentName()); if (!defineArg(funcpn, name, disallowDuplicateArgs, &duplicatedArg)) return false; @@ -2241,7 +2307,7 @@ Parser::functionArguments(YieldHandling yieldHandling, FunctionSyn // before the first default argument. funbox->length = pc->numArgs() - 1; } - Node def_expr = assignExprWithoutYield(yieldHandling, JSMSG_YIELD_IN_DEFAULT); + Node def_expr = assignExprWithoutYieldOrAwait(yieldHandling); if (!def_expr) return false; if (!handler.setLastFunctionArgumentDefault(funcpn, def_expr)) @@ -2745,6 +2811,7 @@ Parser::functionDef(InHandling inHandling, YieldHandling yieldHand Node* assignmentForAnnexBOut) { MOZ_ASSERT_IF(kind == Statement, funName); + MOZ_ASSERT_IF(asyncKind == AsyncFunction, generatorKind == StarGenerator); /* Make a TOK_FUNCTION node. */ Node pn = handler.newFunctionDefinition(); @@ -2890,7 +2957,7 @@ Parser::functionArgsAndBody(InHandling inHandling, ParseNode* if (kind == DerivedClassConstructor) funbox->setDerivedClassConstructor(); - YieldHandling yieldHandling = generatorKind != NotGenerator ? YieldIsKeyword : YieldIsName; + YieldHandling yieldHandling = GetYieldHandling(generatorKind, asyncKind); // We need to roll back the block scope vector if syntax parsing fails. uint32_t oldBlockScopesLength = blockScopes.length(); @@ -2993,7 +3060,7 @@ Parser::functionArgsAndBody(InHandling inHandling, Node pn, if (!funpc.init(*this)) return false; - YieldHandling yieldHandling = generatorKind != NotGenerator ? YieldIsKeyword : YieldIsName; + YieldHandling yieldHandling = GetYieldHandling(generatorKind, asyncKind); if (!functionArgsAndBodyGeneric(inHandling, yieldHandling, pn, fun, kind)) return false; @@ -3038,8 +3105,13 @@ Parser::standaloneLazyFunction(HandleFunction fun, bool strict return null(); // Our tokenStream has no current token, so pn's position is garbage. - // Substitute the position of the first token in our source. - if (!tokenStream.peekTokenPos(&pn->pn_pos)) + // Substitute the position of the first token in our source. If the function + // is a not-async arrow, use TokenStream::Operand to keep + // verifyConsistentModifier from complaining (we will use + // TokenStream::Operand in functionArguments). + TokenStream::Modifier modifier = (fun->isArrow() && asyncKind == SyncFunction) + ? TokenStream::Operand : TokenStream::None; + if (!tokenStream.peekTokenPos(&pn->pn_pos, modifier)) return null(); RootedObject enclosing(context, fun->lazyScript()->enclosingScope()); @@ -3058,7 +3130,7 @@ Parser::standaloneLazyFunction(HandleFunction fun, bool strict if (!funpc.init(*this)) return null(); - YieldHandling yieldHandling = generatorKind != NotGenerator ? YieldIsKeyword : YieldIsName; + YieldHandling yieldHandling = GetYieldHandling(generatorKind, asyncKind); FunctionSyntaxKind syntaxKind = Statement; if (fun->isClassConstructor()) syntaxKind = ClassConstructor; @@ -3102,12 +3174,13 @@ Parser::functionArgsAndBodyGeneric(InHandling inHandling, // function without concern for conversion to strict mode, use of lazy // parsing and such. + FunctionBox* funbox = pc->sc->asFunctionBox(); + AutoAwaitIsKeyword awaitIsKeyword(&tokenStream, funbox->isAsync()); + bool hasRest; if (!functionArguments(yieldHandling, kind, pn, &hasRest)) return false; - FunctionBox* funbox = pc->sc->asFunctionBox(); - fun->setArgCount(pc->numArgs()); if (hasRest) fun->setHasRest(); @@ -3128,7 +3201,7 @@ Parser::functionArgsAndBodyGeneric(InHandling inHandling, if (!tokenStream.getToken(&tt, TokenStream::Operand)) return false; if (tt != TOK_LC) { - if (funbox->isStarGenerator() || kind == Method || + if ((funbox->isStarGenerator() && !funbox->isAsync()) || kind == Method || kind == GetterNoExpressionClosure || kind == SetterNoExpressionClosure || IsConstructorKind(kind)) { report(ParseError, false, null(), JSMSG_CURLY_BEFORE_BODY); @@ -3137,7 +3210,7 @@ Parser::functionArgsAndBodyGeneric(InHandling inHandling, if (kind != Arrow) { #if JS_HAS_EXPR_CLOSURES - addTelemetry(JSCompartment::DeprecatedExpressionClosure); + //addTelemetry(JSCompartment::DeprecatedExpressionClosure); if (!warnOnceAboutExprClosure()) return false; #else @@ -3201,7 +3274,8 @@ Parser::checkYieldNameValidity() template typename ParseHandler::Node -Parser::functionStmt(YieldHandling yieldHandling, DefaultHandling defaultHandling) +Parser::functionStmt(YieldHandling yieldHandling, DefaultHandling defaultHandling, + FunctionAsyncKind asyncKind) { MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_FUNCTION)); @@ -3224,12 +3298,16 @@ Parser::functionStmt(YieldHandling yieldHandling, DefaultHandling } RootedPropertyName name(context); - GeneratorKind generatorKind = NotGenerator; + GeneratorKind generatorKind = asyncKind == AsyncFunction ? StarGenerator : NotGenerator; TokenKind tt; if (!tokenStream.getToken(&tt)) return null(); if (tt == TOK_MUL) { + if (asyncKind != SyncFunction) { + report(ParseError, false, null(), JSMSG_ASYNC_GENERATOR); + return null(); + } generatorKind = StarGenerator; if (!tokenStream.getToken(&tt)) return null(); @@ -3251,8 +3329,9 @@ Parser::functionStmt(YieldHandling yieldHandling, DefaultHandling } Node assignmentForAnnexB; - Node fun = functionDef(InAllowed, yieldHandling, name, Statement, generatorKind, - SyncFunction, PredictUninvoked, &assignmentForAnnexB); + YieldHandling newYieldHandling = GetYieldHandling(generatorKind, asyncKind); + Node fun = functionDef(InAllowed, newYieldHandling, name, Statement, generatorKind, + asyncKind, PredictUninvoked, &assignmentForAnnexB); if (!fun) return null(); @@ -3283,21 +3362,28 @@ Parser::functionStmt(YieldHandling yieldHandling, DefaultHandling template typename ParseHandler::Node -Parser::functionExpr(InvokedPrediction invoked) +Parser::functionExpr(InvokedPrediction invoked, FunctionAsyncKind asyncKind) { MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_FUNCTION)); - GeneratorKind generatorKind = NotGenerator; + AutoAwaitIsKeyword awaitIsKeyword(&tokenStream, asyncKind == AsyncFunction); + GeneratorKind generatorKind = asyncKind == AsyncFunction ? StarGenerator : NotGenerator; TokenKind tt; if (!tokenStream.getToken(&tt)) return null(); if (tt == TOK_MUL) { + if (asyncKind != SyncFunction) { + report(ParseError, false, null(), JSMSG_ASYNC_GENERATOR); + return null(); + } generatorKind = StarGenerator; if (!tokenStream.getToken(&tt)) return null(); } + YieldHandling yieldHandling = GetYieldHandling(generatorKind, asyncKind); + RootedPropertyName name(context); if (tt == TOK_NAME) { name = tokenStream.currentName(); @@ -3309,8 +3395,7 @@ Parser::functionExpr(InvokedPrediction invoked) tokenStream.ungetToken(); } - YieldHandling yieldHandling = generatorKind != NotGenerator ? YieldIsKeyword : YieldIsName; - return functionDef(InAllowed, yieldHandling, name, Expression, generatorKind, SyncFunction, invoked); + return functionDef(InAllowed, yieldHandling, name, Expression, generatorKind, asyncKind, invoked); } /* @@ -4397,16 +4482,21 @@ Parser::destructuringExpr(YieldHandling yieldHandling, BindData typename ParseHandler::Node -Parser::destructuringExprWithoutYield(YieldHandling yieldHandling, - BindData* data, TokenKind tt, - unsigned msg) +Parser::destructuringExprWithoutYieldOrAwait(YieldHandling yieldHandling, + BindData* data, TokenKind tt) { uint32_t startYieldOffset = pc->lastYieldOffset; + uint32_t startAwaitOffset = pc->lastAwaitOffset; Node res = destructuringExpr(yieldHandling, data, tt); - if (res && pc->lastYieldOffset != startYieldOffset) { - reportWithOffset(ParseError, false, pc->lastYieldOffset, - msg, js_yield_str); - return null(); + if (res) { + if (pc->lastYieldOffset != startYieldOffset) { + reportWithOffset(ParseError, false, pc->lastYieldOffset, JSMSG_YIELD_IN_DEFAULT); + return null(); + } + if (pc->lastAwaitOffset != startAwaitOffset) { + reportWithOffset(ParseError, false, pc->lastAwaitOffset, JSMSG_AWAIT_IN_DEFAULT); + return null(); + } } return res; } @@ -5581,7 +5671,24 @@ Parser::exportDeclaration() if (!kid) return null(); break; - default: + default: { + if (tt == TOK_NAME && tokenStream.currentName() == context->names().async) { + TokenKind nextSameLine = TOK_EOF; + if (!tokenStream.peekTokenSameLine(&nextSameLine)) + return null(); + + if (nextSameLine == TOK_FUNCTION) { + if (!warnOnceAboutAsyncFuncs()) + return null(); + + tokenStream.consumeKnownToken(nextSameLine); + kid = functionStmt(YieldIsName, AllowDefaultName, AsyncFunction); + if (!kid) + return null(); + break; + } + } + tokenStream.ungetToken(); RootedPropertyName name(context, context->names().starDefaultStar); binding = makeInitializedLexicalBinding(name, PrepareConst, pos()); @@ -5593,6 +5700,7 @@ Parser::exportDeclaration() if (!MatchOrInsertSemicolonAfterExpression(tokenStream)) return null(); break; + } } return handler.newExportDefaultDeclaration(kid, binding, TokenPos(begin, pos().end)); @@ -6424,6 +6532,18 @@ Parser::newYieldExpression(uint32_t begin, typename ParseHandler:: return handler.newYieldExpression(begin, expr, generator); } +template +typename ParseHandler::Node +Parser::newAwaitExpression(uint32_t begin, typename ParseHandler::Node expr) +{ + Node generator = newName(context->names().dotGenerator); + if (!generator) + return null(); + if (!noteNameUse(context->names().dotGenerator, generator)) + return null(); + return handler.newAwaitExpression(begin, expr, generator); +} + template typename ParseHandler::Node Parser::yieldExpression(InHandling inHandling) @@ -6873,6 +6993,7 @@ JSOpFromPropertyType(PropertyType propType) case PropertyType::Normal: case PropertyType::Method: case PropertyType::GeneratorMethod: + case PropertyType::AsyncMethod: case PropertyType::Constructor: case PropertyType::DerivedConstructor: return JSOP_INITPROP; @@ -6894,8 +7015,8 @@ FunctionSyntaxKindFromPropertyType(PropertyType propType) case PropertyType::SetterNoExpressionClosure: return SetterNoExpressionClosure; case PropertyType::Method: - return Method; case PropertyType::GeneratorMethod: + case PropertyType::AsyncMethod: return Method; case PropertyType::Constructor: return ClassConstructor; @@ -6909,7 +7030,19 @@ FunctionSyntaxKindFromPropertyType(PropertyType propType) static GeneratorKind GeneratorKindFromPropertyType(PropertyType propType) { - return propType == PropertyType::GeneratorMethod ? StarGenerator : NotGenerator; + if (propType == PropertyType::GeneratorMethod) + return StarGenerator; + if (propType == PropertyType::AsyncMethod) + return StarGenerator; + return NotGenerator; +} + +static FunctionAsyncKind +AsyncKindFromPropertyType(PropertyType propType) +{ + if (propType == PropertyType::AsyncMethod) + return AsyncFunction; + return SyncFunction; } template <> @@ -7023,6 +7156,7 @@ Parser::classDefinition(YieldHandling yieldHandling, if (propType != PropertyType::Getter && propType != PropertyType::Setter && propType != PropertyType::Method && propType != PropertyType::GeneratorMethod && + propType != PropertyType::AsyncMethod && propType != PropertyType::Constructor && propType != PropertyType::DerivedConstructor) { report(ParseError, false, null(), JSMSG_BAD_METHOD_DEF); @@ -7183,6 +7317,7 @@ Parser::peekShouldParseLetDeclaration(bool* parseDeclOut, return true; } +/* ::statementListItem */ template typename ParseHandler::Node Parser::statement(YieldHandling yieldHandling, bool canHaveDirectives) @@ -7266,6 +7401,19 @@ Parser::statement(YieldHandling yieldHandling, bool canHaveDirecti return lexicalDeclaration(yieldHandling, /* isConst = */ false); } + if (tokenStream.currentName() == context->names().async) { + TokenKind nextSameLine = TOK_EOF; + if (!tokenStream.peekTokenSameLine(&nextSameLine)) + return null(); + if (nextSameLine == TOK_FUNCTION) { + if (!warnOnceAboutAsyncFuncs()) + return null(); + + tokenStream.consumeKnownToken(TOK_FUNCTION); + return functionStmt(yieldHandling, NameRequired, AsyncFunction); + } + } + TokenKind next; if (!tokenStream.peekToken(&next)) return null(); @@ -7726,6 +7874,16 @@ Parser::assignExpr(InHandling inHandling, YieldHandling yieldHandl if (tt == TOK_YIELD && yieldExpressionsSupported()) return yieldExpression(inHandling); + bool maybeAsyncArrow = false; + if (tt == TOK_NAME && tokenStream.currentName() == context->names().async) { + TokenKind nextSameLine = TOK_EOF; + if (!tokenStream.peekTokenSameLine(&nextSameLine)) + return null(); + + if (nextSameLine == TOK_NAME || nextSameLine == TOK_YIELD) + maybeAsyncArrow = true; + } + tokenStream.ungetToken(); // Save the tokenizer state in case we find an arrow function and have to @@ -7734,9 +7892,35 @@ Parser::assignExpr(InHandling inHandling, YieldHandling yieldHandl tokenStream.tell(&start); PossibleError possibleErrorInner(*this); - Node lhs = condExpr1(inHandling, yieldHandling, tripledotHandling, &possibleErrorInner, invoked); - if (!lhs) { - return null(); + Node lhs; + if (maybeAsyncArrow) { + tokenStream.consumeKnownToken(TOK_NAME, TokenStream::Operand); + MOZ_ASSERT(tokenStream.currentName() == context->names().async); + + TokenKind tt; + if (!tokenStream.getToken(&tt)) + return null(); + MOZ_ASSERT(tt == TOK_NAME || tt == TOK_YIELD); + + // Check yield validity here. + if (!checkYieldNameValidity()) + return null(); + + if (!tokenStream.getToken(&tt)) + return null(); + if (tt != TOK_ARROW) { + report(ParseError, false, null(), JSMSG_UNEXPECTED_TOKEN, + "'=>' after argument list", TokenKindToDesc(tt)); + + return null(); + } + if (!warnOnceAboutAsyncFuncs()) + return null(); + } else { + lhs = condExpr1(inHandling, yieldHandling, tripledotHandling, &possibleErrorInner, invoked); + if (!lhs) { + return null(); + } } ParseNodeKind kind; @@ -7778,11 +7962,35 @@ Parser::assignExpr(InHandling inHandling, YieldHandling yieldHandl if (!abortIfSyntaxParser()) return null(); - TokenKind ignored; - if (!tokenStream.peekToken(&ignored, TokenStream::Operand)) + if (!tokenStream.peekToken(&next, TokenStream::Operand)) return null(); - Node arrowFunc = functionDef(inHandling, yieldHandling, nullptr, Arrow, NotGenerator, SyncFunction); + GeneratorKind generatorKind = NotGenerator; + FunctionAsyncKind asyncKind = SyncFunction; + + if (next == TOK_NAME) { + tokenStream.consumeKnownToken(next, TokenStream::Operand); + + if (tokenStream.currentName() == context->names().async) { + TokenKind nextSameLine = TOK_EOF; + if (!tokenStream.peekTokenSameLine(&nextSameLine)) + return null(); + if (!warnOnceAboutAsyncFuncs()) + return null(); + + if (nextSameLine == TOK_ARROW) { + tokenStream.ungetToken(); + } else { + generatorKind = StarGenerator; + asyncKind = AsyncFunction; + } + } else { + tokenStream.ungetToken(); + } + } + + Node arrowFunc = functionDef(inHandling, yieldHandling, nullptr, + Arrow, generatorKind, asyncKind); if (!arrowFunc) return null(); @@ -7811,6 +8019,7 @@ Parser::assignExpr(InHandling inHandling, YieldHandling yieldHandl // has block body. An arrow function not ending in such, ends in // another AssignmentExpression that we can inductively assume was // peeked consistently. + TokenKind ignored; if (!tokenStream.peekToken(&ignored, TokenStream::Operand)) return null(); tokenStream.addModifierException(TokenStream::NoneIsOperand); @@ -8062,6 +8271,21 @@ Parser::unaryExpr(YieldHandling yieldHandling, TripledotHandling t return handler.newDelete(begin, expr); } + case TOK_AWAIT: { + TokenKind nextSameLine = TOK_EOF; + if (!tokenStream.peekTokenSameLine(&nextSameLine, TokenStream::Operand)) + return null(); + if (nextSameLine != TOK_EOL) { + Node kid = unaryExpr(yieldHandling, tripledotHandling, possibleError, invoked); + if (!kid) + return null(); + pc->lastAwaitOffset = begin; + return newAwaitExpression(begin, kid); + } + report(ParseError, false, null(), JSMSG_LINE_BREAK_AFTER_AWAIT); + return null(); + } + default: { Node pn = memberExpr(yieldHandling, tripledotHandling, possibleError, tt, /* allowCallSyntax = */ true, invoked); @@ -9003,14 +9227,20 @@ Parser::generatorComprehension(uint32_t begin) template typename ParseHandler::Node -Parser::assignExprWithoutYield(YieldHandling yieldHandling, unsigned msg) +Parser::assignExprWithoutYieldOrAwait(YieldHandling yieldHandling) { uint32_t startYieldOffset = pc->lastYieldOffset; + uint32_t startAwaitOffset = pc->lastAwaitOffset; Node res = assignExpr(InAllowed, yieldHandling, TripledotProhibited); - if (res && pc->lastYieldOffset != startYieldOffset) { - reportWithOffset(ParseError, false, pc->lastYieldOffset, - msg, js_yield_str); - return null(); + if (res) { + if (pc->lastYieldOffset != startYieldOffset) { + reportWithOffset(ParseError, false, pc->lastYieldOffset, JSMSG_YIELD_IN_DEFAULT); + return null(); + } + if (pc->lastAwaitOffset != startAwaitOffset) { + reportWithOffset(ParseError, false, pc->lastAwaitOffset, JSMSG_AWAIT_IN_DEFAULT); + return null(); + } } return res; } @@ -9572,15 +9802,53 @@ Parser::propertyName(YieldHandling yieldHandling, Node propList, return null(); // TOK_RC should be handled in caller. - MOZ_ASSERT(ltok != TOK_RC); + MOZ_ASSERT(ltok != TOK_RC, "caller should have handled TOK_RC"); bool isGenerator = false; + bool isAsync = false; if (ltok == TOK_MUL) { isGenerator = true; if (!tokenStream.getToken(<ok, TokenStream::KeywordIsName)) return null(); } + if (ltok == TOK_NAME && tokenStream.currentName() == context->names().async) { + // AsyncMethod[Yield, Await]: + // async [no LineTerminator here] PropertyName[?Yield, ?Await] ... + // + // PropertyName: + // LiteralPropertyName + // ComputedPropertyName[?Yield, ?Await] + // + // LiteralPropertyName: + // IdentifierName + // StringLiteral + // NumericLiteral + // + // ComputedPropertyName[Yield, Await]: + // [ ... + TokenKind tt = TOK_EOF; + if (!tokenStream.peekTokenSameLine(&tt, TokenStream::KeywordIsName)) + return null(); + if (tt == TOK_STRING || tt == TOK_NUMBER || tt == TOK_LB || + tt == TOK_NAME || tt == TOK_YIELD) + { + isAsync = true; + tokenStream.consumeKnownToken(tt, TokenStream::KeywordIsName); + ltok = tt; + } else { + tokenStream.addModifierException(TokenStream::NoneIsKeywordIsName); + } + } + + if (isAsync) { + if (isGenerator) { + report(ParseError, false, null(), JSMSG_ASYNC_GENERATOR); + return null(); + } + if (!warnOnceAboutAsyncFuncs()) return null(); // or fail safe? + } + propAtom.set(nullptr); Node propName; switch (ltok) { @@ -9602,7 +9870,7 @@ Parser::propertyName(YieldHandling yieldHandling, Node propList, case TOK_NAME: { propAtom.set(tokenStream.currentName()); // Do not look for accessor syntax on generators - if (isGenerator || + if (isGenerator || isAsync || !(propAtom.get() == context->names().get || propAtom.get() == context->names().set)) { @@ -9721,7 +9989,12 @@ Parser::propertyName(YieldHandling yieldHandling, Node propList, if (tt == TOK_LP) { tokenStream.ungetToken(); - *propType = isGenerator ? PropertyType::GeneratorMethod : PropertyType::Method; + if (isGenerator) + *propType = PropertyType::GeneratorMethod; + else if (isAsync) + *propType = PropertyType::AsyncMethod; + else + *propType = PropertyType::Method; return propName; } @@ -9899,6 +10172,7 @@ Parser::objectLiteral(YieldHandling yieldHandling, PossibleError* return literal; } +/* XXX: yieldHandling no longer used */ template typename ParseHandler::Node Parser::methodDefinition(YieldHandling yieldHandling, PropertyType propType, @@ -9906,7 +10180,9 @@ Parser::methodDefinition(YieldHandling yieldHandling, PropertyType { FunctionSyntaxKind kind = FunctionSyntaxKindFromPropertyType(propType); GeneratorKind generatorKind = GeneratorKindFromPropertyType(propType); - return functionDef(InAllowed, yieldHandling, funName, kind, generatorKind, SyncFunction); + FunctionAsyncKind asyncKind = AsyncKindFromPropertyType(propType); + YieldHandling newYieldHandling = GetYieldHandling(generatorKind, asyncKind); + return functionDef(InAllowed, newYieldHandling, funName, kind, generatorKind, asyncKind); } template @@ -10030,8 +10306,22 @@ Parser::primaryExpr(YieldHandling yieldHandling, TripledotHandling if (!checkYieldNameValidity()) return null(); // Fall through. - case TOK_NAME: + case TOK_NAME: { + if (tokenStream.currentName() == context->names().async) { + TokenKind nextSameLine = TOK_EOF; + if (!tokenStream.peekTokenSameLine(&nextSameLine)) + return null(); + + if (nextSameLine == TOK_FUNCTION) { + if (!warnOnceAboutAsyncFuncs()) + return null(); + tokenStream.consumeKnownToken(TOK_FUNCTION); + return functionExpr(PredictUninvoked, AsyncFunction); + } + } + return identifierName(yieldHandling); + } case TOK_REGEXP: return newRegExp(); @@ -10233,6 +10523,27 @@ Parser::warnOnceAboutExprClosure() return true; } +template +bool +Parser::warnOnceAboutAsyncFuncs() +{ + JSContext* cx = context->maybeJSContext(); + if (!cx) + return true; + + if (!cx->runtime()->options().asyncFuncs()) { + report(ParseError, false, null(), JSMSG_ASYNC_DISABLED); + return false; + } + + if (!cx->compartment()->warnedAboutAsyncFuncs) { + if (!report(ParseWarning, false, null(), JSMSG_ASYNC_WARNING)) + return false; + cx->compartment()->warnedAboutAsyncFuncs = true; + } + return true; +} + template class Parser; template class Parser; diff --git a/js/src/frontend/Parser.h b/js/src/frontend/Parser.h index 6e2e83f5cf..6b34662a67 100644 --- a/js/src/frontend/Parser.h +++ b/js/src/frontend/Parser.h @@ -116,6 +116,11 @@ struct MOZ_STACK_CLASS ParseContext : public GenericParseContext static const uint32_t NoYieldOffset = UINT32_MAX; uint32_t lastYieldOffset; + // lastAwaitOffset stores the offset of the last await that was parsed. + // NoAwaitOffset is its initial value. + static const uint32_t NoAwaitOffset = UINT32_MAX; + uint32_t lastAwaitOffset; + // Most functions start off being parsed as non-generators. // Non-generators transition to LegacyGenerator on parsing "yield" in JS 1.7. // An ES6 generator is marked as a "star generator" before its body is parsed. @@ -130,6 +135,10 @@ struct MOZ_STACK_CLASS ParseContext : public GenericParseContext return sc->isFunctionBox() && sc->asFunctionBox()->isAsync(); } + FunctionAsyncKind asyncKind() const { + return isAsync() ? AsyncFunction : SyncFunction; + } + bool isArrowFunction() const { return sc->isFunctionBox() && sc->asFunctionBox()->function()->isArrow(); } @@ -267,6 +276,7 @@ struct MOZ_STACK_CLASS ParseContext : public GenericParseContext stmtStack(prs->context), maybeFunction(maybeFunction), lastYieldOffset(NoYieldOffset), + lastAwaitOffset(NoAwaitOffset), blockScopeDepth(0), blockNode(ParseHandler::null()), decls_(prs->context, prs->alloc), @@ -375,6 +385,7 @@ enum class PropertyType { SetterNoExpressionClosure, Method, GeneratorMethod, + AsyncMethod, Constructor, DerivedConstructor }; @@ -653,6 +664,7 @@ class Parser : private JS::AutoGCRooter, public StrictModeGetter inline Node newName(PropertyName* name); inline Node newYieldExpression(uint32_t begin, Node expr, bool isYieldStar = false); + inline Node newAwaitExpression(uint32_t begin, Node expr); inline bool abortIfSyntaxParser(); @@ -705,7 +717,7 @@ class Parser : private JS::AutoGCRooter, public StrictModeGetter // inside a star generator. bool checkYieldNameValidity(); bool yieldExpressionsSupported() { - return versionNumber() >= JSVERSION_1_7 || pc->isGenerator(); + return (versionNumber() >= JSVERSION_1_7 || pc->isGenerator()) && !pc->isAsync(); } virtual bool strictMode() { return pc->sc->strict(); } @@ -739,8 +751,10 @@ class Parser : private JS::AutoGCRooter, public StrictModeGetter * Some parsers have two versions: an always-inlined version (with an 'i' * suffix) and a never-inlined version (with an 'n' suffix). */ - Node functionStmt(YieldHandling yieldHandling, DefaultHandling defaultHandling); - Node functionExpr(InvokedPrediction invoked = PredictUninvoked); + Node functionStmt(YieldHandling yieldHandling, DefaultHandling defaultHandling, + FunctionAsyncKind asyncKind = SyncFunction); + Node functionExpr(InvokedPrediction invoked = PredictUninvoked, + FunctionAsyncKind asyncKind = SyncFunction); Node statements(YieldHandling yieldHandling); Node blockStatement(YieldHandling yieldHandling); @@ -844,7 +858,7 @@ class Parser : private JS::AutoGCRooter, public StrictModeGetter Node assignExpr(InHandling inHandling, YieldHandling yieldHandling, TripledotHandling tripledotHandling, InvokedPrediction invoked = PredictUninvoked); - Node assignExprWithoutYield(YieldHandling yieldHandling, unsigned err); + Node assignExprWithoutYieldOrAwait(YieldHandling yieldHandling); Node yieldExpression(InHandling inHandling); Node condExpr1(InHandling inHandling, YieldHandling yieldHandling, TripledotHandling tripledotHandling, @@ -915,8 +929,8 @@ class Parser : private JS::AutoGCRooter, public StrictModeGetter bool argumentList(YieldHandling yieldHandling, Node listNode, bool* isSpread); Node destructuringExpr(YieldHandling yieldHandling, BindData* data, TokenKind tt); - Node destructuringExprWithoutYield(YieldHandling yieldHandling, BindData* data, - TokenKind tt, unsigned msg); + Node destructuringExprWithoutYieldOrAwait(YieldHandling yieldHandling, BindData* data, + TokenKind tt); Node newBoundImportForCurrentName(); bool namedImportsOrNamespaceImport(TokenKind tt, Node importSpecSet); @@ -1072,6 +1086,7 @@ class Parser : private JS::AutoGCRooter, public StrictModeGetter void addTelemetry(JSCompartment::DeprecatedLanguageExtension e); bool warnOnceAboutExprClosure(); + bool warnOnceAboutAsyncFuncs(); friend class LegacyCompExprTransplanter; friend struct BindData; diff --git a/js/src/frontend/SyntaxParseHandler.h b/js/src/frontend/SyntaxParseHandler.h index 7524b04e7d..622da32461 100644 --- a/js/src/frontend/SyntaxParseHandler.h +++ b/js/src/frontend/SyntaxParseHandler.h @@ -299,6 +299,7 @@ class SyntaxParseHandler bool addClassMethodDefinition(Node literal, Node name, Node fn, JSOp op, bool isStatic) { return true; } Node newYieldExpression(uint32_t begin, Node value, Node gen) { return NodeGeneric; } Node newYieldStarExpression(uint32_t begin, Node value, Node gen) { return NodeGeneric; } + Node newAwaitExpression(uint32_t begin, Node value, Node gen) { return NodeGeneric; } // Statements diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp index 94c07e1fae..dcd1ef8320 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -2067,7 +2067,7 @@ CodeGenerator::visitLambda(LLambda* lir) emitLambdaInit(output, scopeChain, info); if (info.flags & JSFunction::EXTENDED) { - MOZ_ASSERT(info.fun->allowSuperProperty()); + MOZ_ASSERT(info.fun->allowSuperProperty() || info.fun->isAsync()); static_assert(FunctionExtended::NUM_EXTENDED_SLOTS == 2, "All slots must be initialized"); masm.storeValue(UndefinedValue(), Address(output, FunctionExtended::offsetOfExtendedSlot(0))); masm.storeValue(UndefinedValue(), Address(output, FunctionExtended::offsetOfExtendedSlot(1))); diff --git a/js/src/js.msg b/js/src/js.msg index 61fca8bd77..6f755e00ff 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -107,6 +107,8 @@ MSG_DEF(JSMSG_PROTO_NOT_OBJORNULL, 1, JSEXN_TYPEERR, "{0}.prototype is not a MSG_DEF(JSMSG_CANT_CALL_CLASS_CONSTRUCTOR, 0, JSEXN_TYPEERR, "class constructors must be invoked with |new|") MSG_DEF(JSMSG_UNINITIALIZED_THIS, 1, JSEXN_REFERENCEERR, "|this| used uninitialized in {0} class constructor") MSG_DEF(JSMSG_BAD_DERIVED_RETURN, 1, JSEXN_TYPEERR, "derived class constructor returned invalid value {0}") +MSG_DEF(JSMSG_NOT_CALLABLE, 1, JSEXN_TYPEERR, "{0} is not callable") +MSG_DEF(JSMSG_NOT_IMPLEMENTED, 1, JSEXN_INTERNALERR, "{0} is not implemented") // JSON MSG_DEF(JSMSG_JSON_BAD_PARSE, 3, JSEXN_SYNTAXERR, "JSON.parse: {0} at line {1} column {2} of the JSON data") @@ -188,6 +190,10 @@ MSG_DEF(JSMSG_ARRAY_COMP_LEFTSIDE, 0, JSEXN_SYNTAXERR, "invalid array compre MSG_DEF(JSMSG_ARRAY_INIT_TOO_BIG, 0, JSEXN_INTERNALERR, "array initializer too large") MSG_DEF(JSMSG_AS_AFTER_IMPORT_STAR, 0, JSEXN_SYNTAXERR, "missing keyword 'as' after import *") MSG_DEF(JSMSG_AS_AFTER_RESERVED_WORD, 1, JSEXN_SYNTAXERR, "missing keyword 'as' after reserved word '{0}'") +MSG_DEF(JSMSG_ASYNC_GENERATOR, 0, JSEXN_SYNTAXERR, "generator function or method can't be async") +MSG_DEF(JSMSG_ASYNC_DISABLED, 0, JSEXN_SYNTAXERR, "async functions are not enabled in the parser") +MSG_DEF(JSMSG_ASYNC_WARNING, 0, JSEXN_NONE, "async functions are not fully supported in TenFourFox") +MSG_DEF(JSMSG_AWAIT_IN_DEFAULT, 0, JSEXN_SYNTAXERR, "await can't be used in default expression") MSG_DEF(JSMSG_BAD_ANON_GENERATOR_RETURN, 0, JSEXN_TYPEERR, "anonymous generator function returns a value") MSG_DEF(JSMSG_BAD_ARROW_ARGS, 0, JSEXN_SYNTAXERR, "invalid arrow-function arguments (parentheses around the arrow-function may help)") MSG_DEF(JSMSG_BAD_BINDING, 1, JSEXN_SYNTAXERR, "redefining {0} is deprecated") @@ -273,6 +279,7 @@ MSG_DEF(JSMSG_LET_CLASS_BINDING, 0, JSEXN_SYNTAXERR, "'let' is not a valid MSG_DEF(JSMSG_LET_COMP_BINDING, 0, JSEXN_SYNTAXERR, "'let' is not a valid name for a comprehension variable") MSG_DEF(JSMSG_LEXICAL_DECL_NOT_IN_BLOCK, 1, JSEXN_SYNTAXERR, "{0} declaration not directly within block") MSG_DEF(JSMSG_LEXICAL_DECL_LABEL, 1, JSEXN_SYNTAXERR, "{0} declarations cannot be labelled") +MSG_DEF(JSMSG_LINE_BREAK_AFTER_AWAIT, 0, JSEXN_SYNTAXERR, "no line break is allowed after 'await'") MSG_DEF(JSMSG_FUNCTION_LABEL, 0, JSEXN_SYNTAXERR, "functions cannot be labelled") MSG_DEF(JSMSG_SLOPPY_FUNCTION_LABEL, 0, JSEXN_SYNTAXERR, "functions can only be labelled inside blocks") MSG_DEF(JSMSG_LINE_BREAK_AFTER_THROW, 0, JSEXN_SYNTAXERR, "no line break is allowed between 'throw' and its expression") @@ -526,6 +533,10 @@ MSG_DEF(JSMSG_NO_INDEXED_SETTER, 2, JSEXN_TYPEERR, "{0} doesn't have an MSG_DEF(JSMSG_CANT_DELETE_SUPER, 0, JSEXN_REFERENCEERR, "invalid delete involving 'super'") MSG_DEF(JSMSG_REINIT_THIS, 0, JSEXN_REFERENCEERR, "super() called twice in derived class constructor") +// Promise +MSG_DEF(JSMSG_PROMISE_RESOLVED_WITH_ITSELF, 0, JSEXN_TYPEERR, "A promise cannot be resolved with itself") +MSG_DEF(JSMSG_SETTIMEOUT_INTERVAL_NONZERO, 0, JSEXN_TYPEERR, "Intervals other than 0 are not supported") + // Modules MSG_DEF(JSMSG_BAD_DEFAULT_EXPORT, 0, JSEXN_SYNTAXERR, "default export cannot be provided by export *") MSG_DEF(JSMSG_MISSING_INDIRECT_EXPORT, 1, JSEXN_SYNTAXERR, "indirect export '{0}' not found") diff --git a/js/src/jsapi.h b/js/src/jsapi.h index 008ab5c7d6..7cf2dfc452 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -1092,6 +1092,7 @@ class JS_PUBLIC_API(RuntimeOptions) { nativeRegExp_(true), unboxedArrays_(false), asyncStack_(true), + asyncFuncs_(false), werror_(false), strictMode_(false), extraWarnings_(false) @@ -1156,6 +1157,12 @@ class JS_PUBLIC_API(RuntimeOptions) { return *this; } + bool asyncFuncs() const { return asyncFuncs_; } + RuntimeOptions& setAsyncFuncs(bool flag) { + asyncFuncs_ = flag; + return *this; + } + bool werror() const { return werror_; } RuntimeOptions& setWerror(bool flag) { werror_ = flag; @@ -1194,6 +1201,7 @@ class JS_PUBLIC_API(RuntimeOptions) { bool nativeRegExp_ : 1; bool unboxedArrays_ : 1; bool asyncStack_ : 1; + bool asyncFuncs_ : 1; bool werror_ : 1; bool strictMode_ : 1; bool extraWarnings_ : 1; diff --git a/js/src/jscompartment.cpp b/js/src/jscompartment.cpp index 0d72551ef4..de5dcbb7c4 100644 --- a/js/src/jscompartment.cpp +++ b/js/src/jscompartment.cpp @@ -51,6 +51,7 @@ JSCompartment::JSCompartment(Zone* zone, const JS::CompartmentOptions& options = marked(true), warnedAboutFlagsArgument(false), warnedAboutExprClosure(false), + warnedAboutAsyncFuncs(false), addonId(options.addonIdOrNull()), #ifdef DEBUG firedOnNewGlobalObject(false), diff --git a/js/src/jscompartment.h b/js/src/jscompartment.h index f4ae35d8f0..8640ca4cda 100644 --- a/js/src/jscompartment.h +++ b/js/src/jscompartment.h @@ -288,6 +288,7 @@ struct JSCompartment bool marked; bool warnedAboutFlagsArgument; bool warnedAboutExprClosure; + bool warnedAboutAsyncFuncs; // A null add-on ID means that the compartment is not associated with an // add-on. diff --git a/js/src/jsfun.cpp b/js/src/jsfun.cpp index 0a333ac597..d48bf596ba 100644 --- a/js/src/jsfun.cpp +++ b/js/src/jsfun.cpp @@ -998,6 +998,11 @@ js::FunctionToString(JSContext* cx, HandleFunction fun, bool lambdaParen) } } + if (fun->isAsync()) { + if (!out.append("async ")) + return nullptr; + } + bool funIsMethodOrNonArrowLambda = (fun->isLambda() && !fun->isArrow()) || fun->isMethod() || fun->isGetter() || fun->isSetter(); @@ -1007,7 +1012,12 @@ js::FunctionToString(JSContext* cx, HandleFunction fun, bool lambdaParen) return nullptr; } if (!fun->isArrow()) { - if (!(fun->isStarGenerator() ? out.append("function* ") : out.append("function "))) + bool ok; + if (fun->isStarGenerator() && !fun->isAsync()) + ok = out.append("function* "); + else + ok = out.append("function "); + if (!ok) return nullptr; } if (fun->atom()) { diff --git a/js/src/jsprototypes.h b/js/src/jsprototypes.h index 1a79ef67bf..406454f33c 100644 --- a/js/src/jsprototypes.h +++ b/js/src/jsprototypes.h @@ -102,6 +102,7 @@ IF_BDATA(real,imaginary)(SIMD, 41, InitSIMDClass, OCLASP(SI real(TypedArray, 43, InitViaClassSpec, &js::TypedArrayObject::sharedTypedArrayPrototypeClass) \ IF_SAB(real,imaginary)(Atomics, 44, InitAtomicsClass, OCLASP(Atomics)) \ real(SavedFrame, 45, InitViaClassSpec, &js::SavedFrame::class_) \ + real(ShellPromise, 46, InitViaClassSpec, OCLASP(ShellPromise)) \ #define JS_FOR_EACH_PROTOTYPE(macro) JS_FOR_PROTOTYPES(macro,macro) diff --git a/js/src/jsscript.cpp b/js/src/jsscript.cpp index 92af3262ba..c2a7afae27 100644 --- a/js/src/jsscript.cpp +++ b/js/src/jsscript.cpp @@ -3010,6 +3010,7 @@ JSScript::fullyInitFromEmitter(ExclusiveContext* cx, HandleScript script, Byteco MOZ_ASSERT(script->functionNonDelazifying() == funbox->function()); MOZ_ASSERT(script->isGeneratorExp_ == funbox->inGenexpLambda); MOZ_ASSERT(script->generatorKind() == funbox->generatorKind()); + MOZ_ASSERT(script->asyncKind() == funbox->asyncKind()); } else { MOZ_ASSERT(!script->funHasExtensibleScope_); MOZ_ASSERT(!script->funNeedsDeclEnvObject_); diff --git a/js/src/moz.build b/js/src/moz.build index 487911a2a2..46c9397d43 100644 --- a/js/src/moz.build +++ b/js/src/moz.build @@ -345,6 +345,8 @@ UNIFIED_SOURCES += [ # jsarray.cpp and jsatom.cpp cannot be built in unified mode because # xpcshell is broken during packaging when compiled with gcc-4.8.2 +# builtin/Promise.cpp cannot be built in unified mode because it makes +# certain parts of RegExp* go together which shouldn't. # builtin/RegExp.cpp cannot be built in unified mode because it is built # without PGO # frontend/Parser.cpp cannot be built in unified mode because of explicit @@ -357,6 +359,7 @@ UNIFIED_SOURCES += [ # instantiations may or may not be needed depending on what it gets bundled # with. SOURCES += [ + 'builtin/Promise.cpp', 'builtin/RegExp.cpp', 'frontend/Parser.cpp', 'gc/StoreBuffer.cpp', @@ -693,6 +696,7 @@ selfhosted.inputs = [ 'builtin/SelfHostingDefines.h', 'builtin/Utilities.js', 'builtin/Array.js', + 'builtin/AsyncFunctions.js', 'builtin/Date.js', 'builtin/Error.js', 'builtin/Generator.js', @@ -703,6 +707,7 @@ selfhosted.inputs = [ 'builtin/Module.js', 'builtin/Number.js', 'builtin/Object.js', + 'builtin/Promise.js', 'builtin/Reflect.js', 'builtin/RegExp.js', 'builtin/String.js', diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index b0e12be47b..68fd9dae70 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -170,6 +170,7 @@ static bool enableIon = false; static bool enableAsmJS = false; static bool enableNativeRegExp = false; static bool enableUnboxedArrays = false; +static bool enableAsyncFuncs = true; #ifdef JS_GC_ZEAL static char gZealStr[128]; #endif @@ -1690,6 +1691,68 @@ PrintErr(JSContext* cx, unsigned argc, Value* vp) return PrintInternal(cx, args, gErrFile); } +static bool +SetTimeout(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + if (args.length() < 2) { + JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED, + "setTimeout", args.length() == 0 ? "0" : "1", "s"); + return false; + } + + RootedValue rval(cx); + JS::AutoValueVector argValues(cx); + argValues.append(args.get(0)); + argValues.append(args.get(1)); + HandleValueArray arr(argValues); + + JSAtom* setTimeoutAtom; + if (!(setTimeoutAtom = Atomize(cx, "setTimeout", 10))) + return false; + + RootedPropertyName name(cx, setTimeoutAtom->asPropertyName()); + RootedValue selfHostedFun(cx); + + if (!GlobalObject::getIntrinsicValue(cx, cx->global(), name, &selfHostedFun)) + return false; + + RootedObject undef(cx); + if (!JS_CallFunctionValue(cx, undef, selfHostedFun, arr, &rval)) + return false; + + args.rval().set(rval); + return true; +} + +static bool +RunEvents(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + RootedValue rval(cx); + JS::AutoValueVector argValues(cx); + HandleValueArray arr(argValues); + + JSAtom* runEvents; + if (!(runEvents = Atomize(cx, "runEvents", 9))) + return false; + + RootedPropertyName name(cx, runEvents->asPropertyName()); + RootedValue selfHostedFun(cx); + + if (!GlobalObject::getIntrinsicValue(cx, cx->global(), name, &selfHostedFun)) + return false; + + RootedObject undef(cx); + if (!JS_CallFunctionValue(cx, undef, selfHostedFun, arr, &rval)) + return false; + + args.rval().set(rval); + return true; +} + static bool Help(JSContext* cx, unsigned argc, Value* vp); @@ -4861,6 +4924,15 @@ static const JSFunctionSpecWithHelp shell_functions[] = { "dateNow()", " Return the current time with sub-ms precision."), + JS_FN_HELP("setTimeout", SetTimeout, 2, 2, +"setTimeout(fn, timeout)", +" Execute a function after a specified timeout. Currently only 0 is supported."), + + JS_FN_HELP("runEvents", RunEvents, 2, 2, +"runEvents()", +" Run events that were scheduled using setTimeout() calls.\n" +" This call is required, because there is no real event loop."), + JS_FN_HELP("help", Help, 0, 0, "help([name ...])", " Display usage and help messages."), @@ -6244,11 +6316,13 @@ SetRuntimeOptions(JSRuntime* rt, const OptionParser& op) enableAsmJS = !op.getBoolOption("no-asmjs"); enableNativeRegExp = !op.getBoolOption("no-native-regexp"); enableUnboxedArrays = op.getBoolOption("unboxed-arrays"); + enableAsyncFuncs = !op.getBoolOption("no-async-funcs"); JS::RuntimeOptionsRef(rt).setBaseline(enableBaseline) .setIon(enableIon) .setAsmJS(enableAsmJS) .setNativeRegExp(enableNativeRegExp) + .setAsyncFuncs(enableAsyncFuncs) .setUnboxedArrays(enableUnboxedArrays); if (op.getBoolOption("no-unboxed-objects")) @@ -6495,6 +6569,7 @@ SetWorkerRuntimeOptions(JSRuntime* rt) .setIon(enableIon) .setAsmJS(enableAsmJS) .setNativeRegExp(enableNativeRegExp) + .setAsyncFuncs(enableAsyncFuncs) .setUnboxedArrays(enableUnboxedArrays); rt->setOffthreadIonCompilationEnabled(offthreadCompilation); rt->profilingScripts = enableCodeCoverage || enableDisassemblyDumps; @@ -6653,6 +6728,8 @@ main(int argc, char** argv, char** envp) || !op.addBoolOption('\0', "no-asmjs", "Disable asm.js compilation") || !op.addBoolOption('\0', "no-native-regexp", "Disable native regexp compilation") || !op.addBoolOption('\0', "no-unboxed-objects", "Disable creating unboxed plain objects") + || !op.addBoolOption('\0', "no-async-funcs", + "Make async/await a syntax error (TenFourFox #521)") || !op.addBoolOption('\0', "unboxed-arrays", "Allow creating unboxed arrays") || !op.addStringOption('\0', "ion-shared-stubs", "on/off", "Use shared stubs (default: off, on to enable)") diff --git a/js/src/tests/ecma_7/AsyncFunctions/104_basic.js b/js/src/tests/ecma_7/AsyncFunctions/104_basic.js new file mode 100644 index 0000000000..9a06599b01 --- /dev/null +++ b/js/src/tests/ecma_7/AsyncFunctions/104_basic.js @@ -0,0 +1,38 @@ +/* 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/. */ + +/* Basic syntactic tests for issue 521. */ + +var Promise = ShellPromise; + +function assertThrowsSE(code) { + assertThrows(() => Reflect.parse(code), SyntaxError); +} + + assertThrowsSE("'use strict'; async function eval() {}"); + assertThrowsSE("'use strict'; async function arguments() {}"); + assertThrowsSE("async function a(k = super.prop) { }"); + assertThrowsSE("async function a() { super.prop(); }"); + assertThrowsSE("async function a() { super(); }"); + + assertThrowsSE("async function a(k = await 3) {}"); + +async function test() { } +var anon = async function() { } +assertEq(test.name, "test"); +assertEq(anon.name, ""); + +function okok(x, y, z) { return (typeof y === "function"); } +assertEq(okok(5, async function(w) { await w+w; }, "ok"), true); +assertEq(okok(6, (async(w)=>{await w+w}), "ok"), true); +assertEq(okok(7, ()=>{!async function(){ }}, "ok"), true); + +function yoyo(k) { return new Promise(resolve => { resolve(k+1); }); } +async function dodo(k) { return await yoyo(k+1); } +// Just make sure this executes. It currently returns ({ }) in the shell. +dodo(5); + +if (typeof reportCompare === "function") + reportCompare(true, true); + diff --git a/js/src/tests/ecma_7/AsyncFunctions/property.js b/js/src/tests/ecma_7/AsyncFunctions/property.js new file mode 100644 index 0000000000..53f779c330 --- /dev/null +++ b/js/src/tests/ecma_7/AsyncFunctions/property.js @@ -0,0 +1,49 @@ +var BUGNUMBER = 1185106; +var summary = "async name token in property and object destructuring pattern"; + +print(BUGNUMBER + ": " + summary); + +{ + let a = { async: 10 }; + assertEq(a.async, 10); +} + +{ + let a = { async() {} }; + assertEq(a.async instanceof Function, true); + assertEq(a.async.name, "async"); +} + +{ + let async = 11; + let a = { async }; + assertEq(a.async, 11); +} + +{ + let { async } = { async: 12 }; + assertEq(async, 12); +} + +{ + let { async = 13 } = {}; + assertEq(async, 13); +} + +{ + let { async: a = 14 } = {}; + assertEq(a, 14); +} + +{ + let { async, other } = { async: 15, other: 16 }; + assertEq(async, 15); + assertEq(other, 16); + + let a = { async, other }; + assertEq(a.async, 15); + assertEq(a.other, 16); +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/ecma_7/AsyncFunctions/shell.js b/js/src/tests/ecma_7/AsyncFunctions/shell.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/js/src/tests/js1_5/GC/regress-319980-01.js b/js/src/tests/js1_5/GC/regress-319980-01.js index e9e696c8da..c567baa2ce 100644 --- a/js/src/tests/js1_5/GC/regress-319980-01.js +++ b/js/src/tests/js1_5/GC/regress-319980-01.js @@ -10,6 +10,8 @@ var summary = 'GC not called during non-fatal out of memory'; var actual = ''; var expect = 'Normal Exit'; +// NOTE: This test is probably bogus now after TenFourFox issue 521. + printBugNumber(BUGNUMBER); printStatus (summary); print ('This test should never fail explicitly. ' + @@ -23,9 +25,9 @@ var testFuncTimerId; var maxTests = 5; var currTest = 0; -if (typeof setTimeout == 'undefined') +if (typeof shellSetTimeout == 'undefined') { - setTimeout = function() {}; + shellSetTimeout = function() {}; clearTimeout = function() {}; actual = 'Normal Exit'; reportCompare(expect, actual, summary); @@ -36,7 +38,7 @@ else // delay test driver end gDelayTestDriverEnd = true; - setTimeout(testFuncWatcher, 1000); + shellSetTimeout(testFuncWatcher, 1000); } function testFuncWatcher() @@ -60,8 +62,8 @@ function testFuncWatcher() print('Executing test ' + currTest + '\n'); - testFuncWatcherId = setTimeout("testFuncWatcher()", timeOut); - testFuncTimerId = setTimeout(testFunc, interval); + testFuncWatcherId = shellSetTimeout("testFuncWatcher()", timeOut); + testFuncTimerId = shellSetTimeout(testFunc, interval); } @@ -120,7 +122,7 @@ function testFunc() if (testFuncTimerId) { - testFuncTimerId = setTimeout(testFunc, interval); + testFuncTimerId = shellSetTimeout(testFunc, interval); } } diff --git a/js/src/tests/js1_5/Regress/regress-314401.js b/js/src/tests/js1_5/Regress/regress-314401.js index 5fa8ee3924..3d50452ed3 100644 --- a/js/src/tests/js1_5/Regress/regress-314401.js +++ b/js/src/tests/js1_5/Regress/regress-314401.js @@ -6,14 +6,16 @@ //----------------------------------------------------------------------------- var BUGNUMBER = 314401; -var summary = 'setTimeout(eval,0,"",null)|setTimeout(Script,0,"",null) should not crash'; +var summary = 'shellSetTimeout(eval,0,"",null)|shellSetTimeout(Script,0,"",null) should not crash'; var actual = 'No Crash'; var expect = 'No Crash'; +// NOTE: probably a bogus test now after TenFourFox issue 521 + printBugNumber(BUGNUMBER); printStatus (summary); -if (typeof setTimeout == 'undefined') +if (typeof shellSetTimeout == 'undefined') { reportCompare(expect, actual, 'Test Skipped.'); } @@ -24,26 +26,26 @@ else try { - setTimeout(eval, 0, '', null); + shellSetTimeout(eval, 0, '', null); } catch(ex) { printStatus(ex+''); } - reportCompare(expect, actual, 'setTimeout(eval, 0, "", null)'); + reportCompare(expect, actual, 'shellSetTimeout(eval, 0, "", null)'); if (typeof Script != 'undefined') { try { - setTimeout(Script, 0, '', null); + shellSetTimeout(Script, 0, '', null); } catch(ex) { printStatus(ex+''); } - reportCompare(expect, actual, 'setTimeout(Script, 0, "", null)'); + reportCompare(expect, actual, 'shellSetTimeout(Script, 0, "", null)'); } try @@ -68,5 +70,5 @@ else } reportCompare(expect, actual, 'setInterval(Script, 0, "", null)'); } - setTimeout('gDelayTestDriverEnd = false; jsTestDriverEnd();', 0); + shellSetTimeout('gDelayTestDriverEnd = false; jsTestDriverEnd();', 0); } diff --git a/js/src/vm/CommonPropertyNames.h b/js/src/vm/CommonPropertyNames.h index 70a3bfbb49..c38283d4be 100644 --- a/js/src/vm/CommonPropertyNames.h +++ b/js/src/vm/CommonPropertyNames.h @@ -24,6 +24,7 @@ macro(ArrayValues, ArrayValues, "ArrayValues") \ macro(ArrayValuesAt, ArrayValuesAt, "ArrayValuesAt") \ macro(Async, Async, "Async") \ + macro(async, async, "async") \ macro(await, await, "await") \ macro(breakdown, breakdown, "breakdown") \ macro(buffer, buffer, "buffer") \ diff --git a/js/src/vm/GlobalObject.cpp b/js/src/vm/GlobalObject.cpp index a387eaf87f..734629bb4a 100644 --- a/js/src/vm/GlobalObject.cpp +++ b/js/src/vm/GlobalObject.cpp @@ -23,6 +23,7 @@ #include "builtin/MapObject.h" #include "builtin/ModuleObject.h" #include "builtin/Object.h" +#include "builtin/Promise.h" #include "builtin/RegExp.h" #include "builtin/SIMD.h" #include "builtin/SymbolObject.h" diff --git a/js/src/vm/GlobalObject.h b/js/src/vm/GlobalObject.h index 5a183b90fc..345791202b 100644 --- a/js/src/vm/GlobalObject.h +++ b/js/src/vm/GlobalObject.h @@ -389,6 +389,13 @@ class GlobalObject : public NativeObject return &global->getPrototype(JSProto_Symbol).toObject().as(); } + static NativeObject* getOrCreatePromisePrototype(JSContext* cx, Handle global) +{ + if (!ensureConstructor(cx, global, JSProto_ShellPromise)) + return nullptr; + return &global->getPrototype(JSProto_ShellPromise).toObject().as(); + } + static NativeObject* getOrCreateRegExpPrototype(JSContext* cx, Handle global) { if (!ensureConstructor(cx, global, JSProto_RegExp)) return nullptr; diff --git a/js/src/vm/ScopeObject.cpp b/js/src/vm/ScopeObject.cpp index 4e0bfbfed9..2c648b6c83 100644 --- a/js/src/vm/ScopeObject.cpp +++ b/js/src/vm/ScopeObject.cpp @@ -239,7 +239,12 @@ CallObject::createForFunction(JSContext* cx, HandleObject enclosing, HandleFunct * object holding function's name. */ if (callee->isNamedLambda()) { - scopeChain = DeclEnvObject::create(cx, scopeChain, callee); + if (callee->isAsync()) { + RootedFunction fun(cx, &callee->getExtendedSlot(1).toObject().as()); + scopeChain = DeclEnvObject::create(cx, scopeChain, fun); + } else { + scopeChain = DeclEnvObject::create(cx, scopeChain, callee); + } if (!scopeChain) return nullptr; } diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp index b1ed70c10f..ca2d6c337d 100644 --- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -25,6 +25,7 @@ #include "builtin/MapObject.h" #include "builtin/ModuleObject.h" #include "builtin/Object.h" +#include "builtin/Promise.h" #include "builtin/Reflect.h" #include "builtin/SelfHostingDefines.h" #include "builtin/SIMD.h" @@ -493,6 +494,54 @@ intrinsic_GetIteratorPrototype(JSContext* cx, unsigned argc, Value* vp) return true; } +static bool +intrinsic_NewPromise(JSContext* cx, unsigned argc, Value* vp) +{ + return js::PromiseConstructor(cx, argc, vp); +} + +static bool +intrinsic_IsPromise(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 1); + MOZ_ASSERT(args[0].isObject()); + bool isPromise = args[0].toObject().getClass() == &ShellPromiseObject::class_; + + args.rval().setBoolean(isPromise); + return true; +} + +static bool +intrinsic_SetFunName(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 2); + MOZ_ASSERT(args[0].isObject() && args[0].toObject().is()); + MOZ_ASSERT(args[1].isString()); + JSAtom* atom = AtomizeString(cx, args[1].toString()); + if (atom == nullptr) + return false; + RootedFunction fun(cx, &args[0].toObject().as()); + fun->setFlags(fun->flags() & ~JSFunction::HAS_GUESSED_ATOM); + fun->initAtom(atom); + + return true; +} + +static bool +intrinsic_GetFunName(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 1); + MOZ_ASSERT(args[0].isObject() && args[0].toObject().is()); + PropertyName* name = args[0].toObject().as().name(); + if (!name) + args.rval().setUndefined(); + else + args.rval().setString(name); + return true; +} + static bool intrinsic_NewArrayIterator(JSContext* cx, unsigned argc, Value* vp) { @@ -1208,6 +1257,20 @@ CallNonGenericSelfhostedMethod(JSContext* cx, unsigned argc, Value* vp) return CallNonGenericMethod(cx, args); } +bool +intrinsic_SetFunctionExtendedSlot(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 3); + MOZ_ASSERT(args[0].isObject()); + MOZ_ASSERT(args[0].toObject().is()); + MOZ_ASSERT(args[1].isInt32()); + + args[0].toObject().as().setExtendedSlot(args[1].toPrivateUint32(), args[2]); + args.rval().setUndefined(); + return true; +} + /** * Returns the default locale as a well-formed, but not necessarily canonicalized, * BCP-47 language tag. @@ -1552,6 +1615,7 @@ static const JSFunctionSpec intrinsic_functions[] = { JS_FN("_NameForTypedArray", intrinsic_NameForTypedArray, 1,0), JS_FN("DecompileArg", intrinsic_DecompileArg, 2,0), JS_FN("RuntimeDefaultLocale", intrinsic_RuntimeDefaultLocale, 0,0), + JS_FN("SetFunctionExtendedSlot", intrinsic_SetFunctionExtendedSlot, 3,0), JS_FN("LocalTZA", intrinsic_LocalTZA, 0,0), JS_INLINABLE_FN("_IsConstructing", intrinsic_IsConstructing, 0,0, @@ -1661,6 +1725,12 @@ static const JSFunctionSpec intrinsic_functions[] = { JS_FN("CallWeakSetMethodIfWrapped", CallNonGenericSelfhostedMethod>, 2, 0), + JS_FN("NewPromise", intrinsic_NewPromise, 1,0), + JS_FN("IsPromise", intrinsic_IsPromise, 1,0), + + JS_FN("SetFunName", intrinsic_SetFunName, 2,0), + JS_FN("GetFunName", intrinsic_GetFunName, 1,0), + // See builtin/TypedObject.h for descriptors of the typedobj functions. JS_FN("NewOpaqueTypedObject", js::NewOpaqueTypedObject, 1, 0), JS_FN("NewDerivedTypedObject", js::NewDerivedTypedObject, 3, 0), diff --git a/js/src/vm/Xdr.h b/js/src/vm/Xdr.h index 2815d31022..f815c1c051 100644 --- a/js/src/vm/Xdr.h +++ b/js/src/vm/Xdr.h @@ -29,11 +29,11 @@ namespace js { * * https://developer.mozilla.org/en-US/docs/SpiderMonkey/Internals/Bytecode */ -static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 338; +static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 339; static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - XDR_BYTECODE_VERSION_SUBTRAHEND); -static_assert(JSErr_Limit == 435, +static_assert(JSErr_Limit == 444, "GREETINGS, POTENTIAL SUBTRAHEND INCREMENTER! If you added or " "removed MSG_DEFs from js.msg, you should increment " "XDR_BYTECODE_VERSION_SUBTRAHEND and update this assertion's " diff --git a/js/xpconnect/src/XPCJSRuntime.cpp b/js/xpconnect/src/XPCJSRuntime.cpp index d501a2a2cf..891a45038c 100644 --- a/js/xpconnect/src/XPCJSRuntime.cpp +++ b/js/xpconnect/src/XPCJSRuntime.cpp @@ -1564,6 +1564,8 @@ ReloadPrefsCallback(const char* pref, void* data) bool useAsyncStack = Preferences::GetBool(JS_OPTIONS_DOT_STR "asyncstack"); + bool useAsyncFuncs = Preferences::GetBool(JS_OPTIONS_DOT_STR "asyncfuncs"); + bool werror = Preferences::GetBool(JS_OPTIONS_DOT_STR "werror"); bool extraWarnings = Preferences::GetBool(JS_OPTIONS_DOT_STR "strict"); @@ -1577,6 +1579,7 @@ ReloadPrefsCallback(const char* pref, void* data) .setThrowOnAsmJSValidationFailure(throwOnAsmJSValidationFailure) .setNativeRegExp(useNativeRegExp) .setAsyncStack(useAsyncStack) + .setAsyncFuncs(useAsyncFuncs) .setWerror(werror) .setExtraWarnings(extraWarnings); diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index 326cb46583..e78f8545ef 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -1132,6 +1132,7 @@ pref("javascript.options.asyncstack", true); #else pref("javascript.options.asyncstack", false); #endif +pref("javascript.options.asyncfuncs", true); // TenFourFox issue 521 pref("javascript.options.throw_on_asmjs_validation_failure", false); pref("javascript.options.ion.offthread_compilation", true); // This preference instructs the JS engine to discard the