diff options
Diffstat (limited to 'js/src/builtin/ReflectParse.cpp')
-rw-r--r-- | js/src/builtin/ReflectParse.cpp | 3752 |
1 files changed, 3752 insertions, 0 deletions
diff --git a/js/src/builtin/ReflectParse.cpp b/js/src/builtin/ReflectParse.cpp new file mode 100644 index 000000000..748ff7351 --- /dev/null +++ b/js/src/builtin/ReflectParse.cpp @@ -0,0 +1,3752 @@ +/* -*- 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/. */ + +/* JS reflection package. */ + +#include "mozilla/ArrayUtils.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/Move.h" + +#include <stdlib.h> + +#include "jsarray.h" +#include "jsatom.h" +#include "jsobj.h" +#include "jspubtd.h" + +#include "builtin/Reflect.h" +#include "frontend/Parser.h" +#include "frontend/TokenStream.h" +#include "js/CharacterEncoding.h" +#include "vm/RegExpObject.h" + +#include "jsobjinlines.h" + +#include "frontend/ParseNode-inl.h" + +using namespace js; +using namespace js::frontend; + +using JS::AutoValueArray; +using mozilla::ArrayLength; +using mozilla::DebugOnly; +using mozilla::Forward; + +enum class ParseTarget +{ + Script, + Module +}; + +enum ASTType { + AST_ERROR = -1, +#define ASTDEF(ast, str, method) ast, +#include "jsast.tbl" +#undef ASTDEF + AST_LIMIT +}; + +enum AssignmentOperator { + AOP_ERR = -1, + + /* assign */ + AOP_ASSIGN = 0, + /* operator-assign */ + AOP_PLUS, AOP_MINUS, AOP_STAR, AOP_DIV, AOP_MOD, AOP_POW, + /* shift-assign */ + AOP_LSH, AOP_RSH, AOP_URSH, + /* binary */ + AOP_BITOR, AOP_BITXOR, AOP_BITAND, + + AOP_LIMIT +}; + +enum BinaryOperator { + BINOP_ERR = -1, + + /* eq */ + BINOP_EQ = 0, BINOP_NE, BINOP_STRICTEQ, BINOP_STRICTNE, + /* rel */ + BINOP_LT, BINOP_LE, BINOP_GT, BINOP_GE, + /* shift */ + BINOP_LSH, BINOP_RSH, BINOP_URSH, + /* arithmetic */ + BINOP_ADD, BINOP_SUB, BINOP_STAR, BINOP_DIV, BINOP_MOD, BINOP_POW, + /* binary */ + BINOP_BITOR, BINOP_BITXOR, BINOP_BITAND, + /* misc */ + BINOP_IN, BINOP_INSTANCEOF, + + BINOP_LIMIT +}; + +enum UnaryOperator { + UNOP_ERR = -1, + + UNOP_DELETE = 0, + UNOP_NEG, + UNOP_POS, + UNOP_NOT, + UNOP_BITNOT, + UNOP_TYPEOF, + UNOP_VOID, + UNOP_AWAIT, + + UNOP_LIMIT +}; + +enum VarDeclKind { + VARDECL_ERR = -1, + VARDECL_VAR = 0, + VARDECL_CONST, + VARDECL_LET, + VARDECL_LIMIT +}; + +enum PropKind { + PROP_ERR = -1, + PROP_INIT = 0, + PROP_GETTER, + PROP_SETTER, + PROP_MUTATEPROTO, + PROP_LIMIT +}; + +static const char* const aopNames[] = { + "=", /* AOP_ASSIGN */ + "+=", /* AOP_PLUS */ + "-=", /* AOP_MINUS */ + "*=", /* AOP_STAR */ + "/=", /* AOP_DIV */ + "%=", /* AOP_MOD */ + "**=", /* AOP_POW */ + "<<=", /* AOP_LSH */ + ">>=", /* AOP_RSH */ + ">>>=", /* AOP_URSH */ + "|=", /* AOP_BITOR */ + "^=", /* AOP_BITXOR */ + "&=" /* AOP_BITAND */ +}; + +static const char* const binopNames[] = { + "==", /* BINOP_EQ */ + "!=", /* BINOP_NE */ + "===", /* BINOP_STRICTEQ */ + "!==", /* BINOP_STRICTNE */ + "<", /* BINOP_LT */ + "<=", /* BINOP_LE */ + ">", /* BINOP_GT */ + ">=", /* BINOP_GE */ + "<<", /* BINOP_LSH */ + ">>", /* BINOP_RSH */ + ">>>", /* BINOP_URSH */ + "+", /* BINOP_PLUS */ + "-", /* BINOP_MINUS */ + "*", /* BINOP_STAR */ + "/", /* BINOP_DIV */ + "%", /* BINOP_MOD */ + "**", /* BINOP_POW */ + "|", /* BINOP_BITOR */ + "^", /* BINOP_BITXOR */ + "&", /* BINOP_BITAND */ + "in", /* BINOP_IN */ + "instanceof", /* BINOP_INSTANCEOF */ +}; + +static const char* const unopNames[] = { + "delete", /* UNOP_DELETE */ + "-", /* UNOP_NEG */ + "+", /* UNOP_POS */ + "!", /* UNOP_NOT */ + "~", /* UNOP_BITNOT */ + "typeof", /* UNOP_TYPEOF */ + "void", /* UNOP_VOID */ + "await" /* UNOP_AWAIT */ +}; + +static const char* const nodeTypeNames[] = { +#define ASTDEF(ast, str, method) str, +#include "jsast.tbl" +#undef ASTDEF + nullptr +}; + +static const char* const callbackNames[] = { +#define ASTDEF(ast, str, method) method, +#include "jsast.tbl" +#undef ASTDEF + nullptr +}; + +enum YieldKind { Delegating, NotDelegating }; + +typedef AutoValueVector NodeVector; + +/* + * ParseNode is a somewhat intricate data structure, and its invariants have + * evolved, making it more likely that there could be a disconnect between the + * parser and the AST serializer. We use these macros to check invariants on a + * parse node and raise a dynamic error on failure. + */ +#define LOCAL_ASSERT(expr) \ + JS_BEGIN_MACRO \ + MOZ_ASSERT(expr); \ + if (!(expr)) { \ + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_PARSE_NODE);\ + return false; \ + } \ + JS_END_MACRO + +#define LOCAL_NOT_REACHED(expr) \ + JS_BEGIN_MACRO \ + MOZ_ASSERT(false); \ + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_PARSE_NODE); \ + return false; \ + JS_END_MACRO + +namespace { + +/* Set 'result' to obj[id] if any such property exists, else defaultValue. */ +static bool +GetPropertyDefault(JSContext* cx, HandleObject obj, HandleId id, HandleValue defaultValue, + MutableHandleValue result) +{ + bool found; + if (!HasProperty(cx, obj, id, &found)) + return false; + if (!found) { + result.set(defaultValue); + return true; + } + return GetProperty(cx, obj, obj, id, result); +} + +enum class GeneratorStyle +{ + None, + Legacy, + ES6 +}; + +/* + * Builder class that constructs JavaScript AST node objects. See: + * + * https://developer.mozilla.org/en/SpiderMonkey/Parser_API + * + * Bug 569487: generalize builder interface + */ +class NodeBuilder +{ + typedef AutoValueArray<AST_LIMIT> CallbackArray; + + JSContext* cx; + TokenStream* tokenStream; + bool saveLoc; /* save source location information? */ + char const* src; /* source filename or null */ + RootedValue srcval; /* source filename JS value or null */ + CallbackArray callbacks; /* user-specified callbacks */ + RootedValue userv; /* user-specified builder object or null */ + + public: + NodeBuilder(JSContext* c, bool l, char const* s) + : cx(c), tokenStream(nullptr), saveLoc(l), src(s), srcval(c), callbacks(cx), + userv(c) + {} + + MOZ_MUST_USE bool init(HandleObject userobj = nullptr) { + if (src) { + if (!atomValue(src, &srcval)) + return false; + } else { + srcval.setNull(); + } + + if (!userobj) { + userv.setNull(); + for (unsigned i = 0; i < AST_LIMIT; i++) { + callbacks[i].setNull(); + } + return true; + } + + userv.setObject(*userobj); + + RootedValue nullVal(cx, NullValue()); + RootedValue funv(cx); + for (unsigned i = 0; i < AST_LIMIT; i++) { + const char* name = callbackNames[i]; + RootedAtom atom(cx, Atomize(cx, name, strlen(name))); + if (!atom) + return false; + RootedId id(cx, AtomToId(atom)); + if (!GetPropertyDefault(cx, userobj, id, nullVal, &funv)) + return false; + + if (funv.isNullOrUndefined()) { + callbacks[i].setNull(); + continue; + } + + if (!funv.isObject() || !funv.toObject().is<JSFunction>()) { + ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_NOT_FUNCTION, + JSDVG_SEARCH_STACK, funv, nullptr, nullptr, nullptr); + return false; + } + + callbacks[i].set(funv); + } + + return true; + } + + void setTokenStream(TokenStream* ts) { + tokenStream = ts; + } + + private: + MOZ_MUST_USE bool callbackHelper(HandleValue fun, const InvokeArgs& args, size_t i, + TokenPos* pos, MutableHandleValue dst) + { + // The end of the implementation of callback(). All arguments except + // loc have already been stored in range [0, i). + if (saveLoc) { + if (!newNodeLoc(pos, args[i])) + return false; + } + + return js::Call(cx, fun, userv, args, dst); + } + + // Helper function for callback(). Note that all Arguments must be types + // that convert to HandleValue, so this isn't as template-y as it seems, + // just variadic. + template <typename... Arguments> + MOZ_MUST_USE bool callbackHelper(HandleValue fun, const InvokeArgs& args, size_t i, + HandleValue head, Arguments&&... tail) + { + // Recursive loop to store the arguments into args. This eventually + // bottoms out in a call to the non-template callbackHelper() above. + args[i].set(head); + return callbackHelper(fun, args, i + 1, Forward<Arguments>(tail)...); + } + + // Invoke a user-defined callback. The actual signature is: + // + // bool callback(HandleValue fun, HandleValue... args, TokenPos* pos, + // MutableHandleValue dst); + template <typename... Arguments> + MOZ_MUST_USE bool callback(HandleValue fun, Arguments&&... args) { + InvokeArgs iargs(cx); + if (!iargs.init(cx, sizeof...(args) - 2 + size_t(saveLoc))) + return false; + + return callbackHelper(fun, iargs, 0, Forward<Arguments>(args)...); + } + + // WARNING: Returning a Handle is non-standard, but it works in this case + // because both |v| and |UndefinedHandleValue| are definitely rooted on a + // previous stack frame (i.e. we're just choosing between two + // already-rooted values). + HandleValue opt(HandleValue v) { + MOZ_ASSERT_IF(v.isMagic(), v.whyMagic() == JS_SERIALIZE_NO_NODE); + return v.isMagic(JS_SERIALIZE_NO_NODE) ? JS::UndefinedHandleValue : v; + } + + MOZ_MUST_USE bool atomValue(const char* s, MutableHandleValue dst) { + /* + * Bug 575416: instead of Atomize, lookup constant atoms in tbl file + */ + RootedAtom atom(cx, Atomize(cx, s, strlen(s))); + if (!atom) + return false; + + dst.setString(atom); + return true; + } + + MOZ_MUST_USE bool newObject(MutableHandleObject dst) { + RootedPlainObject nobj(cx, NewBuiltinClassInstance<PlainObject>(cx)); + if (!nobj) + return false; + + dst.set(nobj); + return true; + } + + MOZ_MUST_USE bool newArray(NodeVector& elts, MutableHandleValue dst); + + MOZ_MUST_USE bool createNode(ASTType type, TokenPos* pos, MutableHandleObject dst); + + MOZ_MUST_USE bool newNodeHelper(HandleObject obj, MutableHandleValue dst) { + // The end of the implementation of newNode(). + MOZ_ASSERT(obj); + dst.setObject(*obj); + return true; + } + + template <typename... Arguments> + MOZ_MUST_USE bool newNodeHelper(HandleObject obj, const char *name, HandleValue value, + Arguments&&... rest) + { + // Recursive loop to define properties. Note that the newNodeHelper() + // call below passes two fewer arguments than we received, as we omit + // `name` and `value`. This eventually bottoms out in a call to the + // non-template newNodeHelper() above. + return defineProperty(obj, name, value) + && newNodeHelper(obj, Forward<Arguments>(rest)...); + } + + // Create a node object with "type" and "loc" properties, as well as zero + // or more properties passed in as arguments. The signature is really more + // like: + // + // bool newNode(ASTType type, TokenPos* pos, + // {const char *name0, HandleValue value0,}... + // MutableHandleValue dst); + template <typename... Arguments> + MOZ_MUST_USE bool newNode(ASTType type, TokenPos* pos, Arguments&&... args) { + RootedObject node(cx); + return createNode(type, pos, &node) && + newNodeHelper(node, Forward<Arguments>(args)...); + } + + MOZ_MUST_USE bool listNode(ASTType type, const char* propName, NodeVector& elts, TokenPos* pos, + MutableHandleValue dst) { + RootedValue array(cx); + if (!newArray(elts, &array)) + return false; + + RootedValue cb(cx, callbacks[type]); + if (!cb.isNull()) + return callback(cb, array, pos, dst); + + return newNode(type, pos, propName, array, dst); + } + + MOZ_MUST_USE bool defineProperty(HandleObject obj, const char* name, HandleValue val) { + MOZ_ASSERT_IF(val.isMagic(), val.whyMagic() == JS_SERIALIZE_NO_NODE); + + /* + * Bug 575416: instead of Atomize, lookup constant atoms in tbl file + */ + RootedAtom atom(cx, Atomize(cx, name, strlen(name))); + if (!atom) + return false; + + /* Represent "no node" as null and ensure users are not exposed to magic values. */ + RootedValue optVal(cx, val.isMagic(JS_SERIALIZE_NO_NODE) ? NullValue() : val); + return DefineProperty(cx, obj, atom->asPropertyName(), optVal); + } + + MOZ_MUST_USE bool newNodeLoc(TokenPos* pos, MutableHandleValue dst); + + MOZ_MUST_USE bool setNodeLoc(HandleObject node, TokenPos* pos); + + public: + /* + * All of the public builder methods take as their last two + * arguments a nullable token position and a non-nullable, rooted + * outparam. + * + * Any Value arguments representing optional subnodes may be a + * JS_SERIALIZE_NO_NODE magic value. + */ + + /* + * misc nodes + */ + + MOZ_MUST_USE bool program(NodeVector& elts, TokenPos* pos, MutableHandleValue dst); + + MOZ_MUST_USE bool literal(HandleValue val, TokenPos* pos, MutableHandleValue dst); + + MOZ_MUST_USE bool identifier(HandleValue name, TokenPos* pos, MutableHandleValue dst); + + MOZ_MUST_USE bool function(ASTType type, TokenPos* pos, + HandleValue id, NodeVector& args, NodeVector& defaults, + HandleValue body, HandleValue rest, GeneratorStyle generatorStyle, + bool isAsync, bool isExpression, MutableHandleValue dst); + + MOZ_MUST_USE bool variableDeclarator(HandleValue id, HandleValue init, TokenPos* pos, + MutableHandleValue dst); + + MOZ_MUST_USE bool switchCase(HandleValue expr, NodeVector& elts, TokenPos* pos, MutableHandleValue dst); + + MOZ_MUST_USE bool catchClause(HandleValue var, HandleValue guard, HandleValue body, TokenPos* pos, + MutableHandleValue dst); + + MOZ_MUST_USE bool prototypeMutation(HandleValue val, TokenPos* pos, MutableHandleValue dst); + MOZ_MUST_USE bool propertyInitializer(HandleValue key, HandleValue val, PropKind kind, + bool isShorthand, bool isMethod, TokenPos* pos, + MutableHandleValue dst); + + + /* + * statements + */ + + MOZ_MUST_USE bool blockStatement(NodeVector& elts, TokenPos* pos, MutableHandleValue dst); + + MOZ_MUST_USE bool expressionStatement(HandleValue expr, TokenPos* pos, MutableHandleValue dst); + + MOZ_MUST_USE bool emptyStatement(TokenPos* pos, MutableHandleValue dst); + + MOZ_MUST_USE bool ifStatement(HandleValue test, HandleValue cons, HandleValue alt, TokenPos* pos, + MutableHandleValue dst); + + MOZ_MUST_USE bool breakStatement(HandleValue label, TokenPos* pos, MutableHandleValue dst); + + MOZ_MUST_USE bool continueStatement(HandleValue label, TokenPos* pos, MutableHandleValue dst); + + MOZ_MUST_USE bool labeledStatement(HandleValue label, HandleValue stmt, TokenPos* pos, + MutableHandleValue dst); + + MOZ_MUST_USE bool throwStatement(HandleValue arg, TokenPos* pos, MutableHandleValue dst); + + MOZ_MUST_USE bool returnStatement(HandleValue arg, TokenPos* pos, MutableHandleValue dst); + + MOZ_MUST_USE bool forStatement(HandleValue init, HandleValue test, HandleValue update, HandleValue stmt, + TokenPos* pos, MutableHandleValue dst); + + MOZ_MUST_USE bool forInStatement(HandleValue var, HandleValue expr, HandleValue stmt, + bool isForEach, TokenPos* pos, MutableHandleValue dst); + + MOZ_MUST_USE bool forOfStatement(HandleValue var, HandleValue expr, HandleValue stmt, TokenPos* pos, + MutableHandleValue dst); + + MOZ_MUST_USE bool withStatement(HandleValue expr, HandleValue stmt, TokenPos* pos, MutableHandleValue dst); + + MOZ_MUST_USE bool whileStatement(HandleValue test, HandleValue stmt, TokenPos* pos, MutableHandleValue dst); + + MOZ_MUST_USE bool doWhileStatement(HandleValue stmt, HandleValue test, TokenPos* pos, + MutableHandleValue dst); + + MOZ_MUST_USE bool switchStatement(HandleValue disc, NodeVector& elts, bool lexical, TokenPos* pos, + MutableHandleValue dst); + + MOZ_MUST_USE bool tryStatement(HandleValue body, NodeVector& guarded, HandleValue unguarded, + HandleValue finally, TokenPos* pos, MutableHandleValue dst); + + MOZ_MUST_USE bool debuggerStatement(TokenPos* pos, MutableHandleValue dst); + + MOZ_MUST_USE bool importDeclaration(NodeVector& elts, HandleValue moduleSpec, TokenPos* pos, MutableHandleValue dst); + + MOZ_MUST_USE bool importSpecifier(HandleValue importName, HandleValue bindingName, TokenPos* pos, MutableHandleValue dst); + + MOZ_MUST_USE bool exportDeclaration(HandleValue decl, NodeVector& elts, HandleValue moduleSpec, + HandleValue isDefault, TokenPos* pos, MutableHandleValue dst); + + MOZ_MUST_USE bool exportSpecifier(HandleValue bindingName, HandleValue exportName, TokenPos* pos, MutableHandleValue dst); + + MOZ_MUST_USE bool exportBatchSpecifier(TokenPos* pos, MutableHandleValue dst); + + MOZ_MUST_USE bool classDefinition(bool expr, HandleValue name, HandleValue heritage, + HandleValue block, TokenPos* pos, MutableHandleValue dst); + MOZ_MUST_USE bool classMethods(NodeVector& methods, MutableHandleValue dst); + MOZ_MUST_USE bool classMethod(HandleValue name, HandleValue body, PropKind kind, bool isStatic, + TokenPos* pos, MutableHandleValue dst); + + /* + * expressions + */ + + MOZ_MUST_USE bool binaryExpression(BinaryOperator op, HandleValue left, HandleValue right, + TokenPos* pos, MutableHandleValue dst); + + MOZ_MUST_USE bool unaryExpression(UnaryOperator op, HandleValue expr, TokenPos* pos, + MutableHandleValue dst); + + MOZ_MUST_USE bool assignmentExpression(AssignmentOperator op, HandleValue lhs, HandleValue rhs, + TokenPos* pos, MutableHandleValue dst); + + MOZ_MUST_USE bool updateExpression(HandleValue expr, bool incr, bool prefix, TokenPos* pos, + MutableHandleValue dst); + + MOZ_MUST_USE bool logicalExpression(bool lor, HandleValue left, HandleValue right, TokenPos* pos, + MutableHandleValue dst); + + MOZ_MUST_USE bool conditionalExpression(HandleValue test, HandleValue cons, HandleValue alt, + TokenPos* pos, MutableHandleValue dst); + + MOZ_MUST_USE bool sequenceExpression(NodeVector& elts, TokenPos* pos, MutableHandleValue dst); + + MOZ_MUST_USE bool newExpression(HandleValue callee, NodeVector& args, TokenPos* pos, + MutableHandleValue dst); + + MOZ_MUST_USE bool callExpression(HandleValue callee, NodeVector& args, TokenPos* pos, + MutableHandleValue dst); + + MOZ_MUST_USE bool memberExpression(bool computed, HandleValue expr, HandleValue member, + TokenPos* pos, MutableHandleValue dst); + + MOZ_MUST_USE bool arrayExpression(NodeVector& elts, TokenPos* pos, MutableHandleValue dst); + + MOZ_MUST_USE bool templateLiteral(NodeVector& elts, TokenPos* pos, MutableHandleValue dst); + + MOZ_MUST_USE bool taggedTemplate(HandleValue callee, NodeVector& args, TokenPos* pos, + MutableHandleValue dst); + + MOZ_MUST_USE bool callSiteObj(NodeVector& raw, NodeVector& cooked, TokenPos* pos, + MutableHandleValue dst); + + MOZ_MUST_USE bool spreadExpression(HandleValue expr, TokenPos* pos, MutableHandleValue dst); + + MOZ_MUST_USE bool computedName(HandleValue name, TokenPos* pos, MutableHandleValue dst); + + MOZ_MUST_USE bool objectExpression(NodeVector& elts, TokenPos* pos, MutableHandleValue dst); + + MOZ_MUST_USE bool thisExpression(TokenPos* pos, MutableHandleValue dst); + + MOZ_MUST_USE bool yieldExpression(HandleValue arg, YieldKind kind, TokenPos* pos, + MutableHandleValue dst); + + MOZ_MUST_USE bool comprehensionBlock(HandleValue patt, HandleValue src, bool isForEach, + bool isForOf, TokenPos* pos, MutableHandleValue dst); + MOZ_MUST_USE bool comprehensionIf(HandleValue test, TokenPos* pos, MutableHandleValue dst); + + MOZ_MUST_USE bool comprehensionExpression(HandleValue body, NodeVector& blocks, + HandleValue filter, bool isLegacy, TokenPos* pos, + MutableHandleValue dst); + + MOZ_MUST_USE bool generatorExpression(HandleValue body, NodeVector& blocks, HandleValue filter, + bool isLegacy, TokenPos* pos, MutableHandleValue dst); + + MOZ_MUST_USE bool metaProperty(HandleValue meta, HandleValue property, TokenPos* pos, + MutableHandleValue dst); + + MOZ_MUST_USE bool super(TokenPos* pos, MutableHandleValue dst); + + /* + * declarations + */ + + MOZ_MUST_USE bool variableDeclaration(NodeVector& elts, VarDeclKind kind, TokenPos* pos, + MutableHandleValue dst); + + /* + * patterns + */ + + MOZ_MUST_USE bool arrayPattern(NodeVector& elts, TokenPos* pos, MutableHandleValue dst); + + MOZ_MUST_USE bool objectPattern(NodeVector& elts, TokenPos* pos, MutableHandleValue dst); + + MOZ_MUST_USE bool propertyPattern(HandleValue key, HandleValue patt, bool isShorthand, + TokenPos* pos, MutableHandleValue dst); +}; + +} /* anonymous namespace */ + +bool +NodeBuilder::createNode(ASTType type, TokenPos* pos, MutableHandleObject dst) +{ + MOZ_ASSERT(type > AST_ERROR && type < AST_LIMIT); + + RootedValue tv(cx); + RootedPlainObject node(cx, NewBuiltinClassInstance<PlainObject>(cx)); + if (!node || + !setNodeLoc(node, pos) || + !atomValue(nodeTypeNames[type], &tv) || + !defineProperty(node, "type", tv)) { + return false; + } + + dst.set(node); + return true; +} + +bool +NodeBuilder::newArray(NodeVector& elts, MutableHandleValue dst) +{ + const size_t len = elts.length(); + if (len > UINT32_MAX) { + ReportAllocationOverflow(cx); + return false; + } + RootedObject array(cx, NewDenseFullyAllocatedArray(cx, uint32_t(len))); + if (!array) + return false; + + for (size_t i = 0; i < len; i++) { + RootedValue val(cx, elts[i]); + + MOZ_ASSERT_IF(val.isMagic(), val.whyMagic() == JS_SERIALIZE_NO_NODE); + + /* Represent "no node" as an array hole by not adding the value. */ + if (val.isMagic(JS_SERIALIZE_NO_NODE)) + continue; + + if (!DefineElement(cx, array, i, val)) + return false; + } + + dst.setObject(*array); + return true; +} + +bool +NodeBuilder::newNodeLoc(TokenPos* pos, MutableHandleValue dst) +{ + if (!pos) { + dst.setNull(); + return true; + } + + RootedObject loc(cx); + RootedObject to(cx); + RootedValue val(cx); + + if (!newObject(&loc)) + return false; + + dst.setObject(*loc); + + uint32_t startLineNum, startColumnIndex; + uint32_t endLineNum, endColumnIndex; + tokenStream->srcCoords.lineNumAndColumnIndex(pos->begin, &startLineNum, &startColumnIndex); + tokenStream->srcCoords.lineNumAndColumnIndex(pos->end, &endLineNum, &endColumnIndex); + + if (!newObject(&to)) + return false; + val.setObject(*to); + if (!defineProperty(loc, "start", val)) + return false; + val.setNumber(startLineNum); + if (!defineProperty(to, "line", val)) + return false; + val.setNumber(startColumnIndex); + if (!defineProperty(to, "column", val)) + return false; + + if (!newObject(&to)) + return false; + val.setObject(*to); + if (!defineProperty(loc, "end", val)) + return false; + val.setNumber(endLineNum); + if (!defineProperty(to, "line", val)) + return false; + val.setNumber(endColumnIndex); + if (!defineProperty(to, "column", val)) + return false; + + if (!defineProperty(loc, "source", srcval)) + return false; + + return true; +} + +bool +NodeBuilder::setNodeLoc(HandleObject node, TokenPos* pos) +{ + if (!saveLoc) { + RootedValue nullVal(cx, NullValue()); + return defineProperty(node, "loc", nullVal); + } + + RootedValue loc(cx); + return newNodeLoc(pos, &loc) && + defineProperty(node, "loc", loc); +} + +bool +NodeBuilder::program(NodeVector& elts, TokenPos* pos, MutableHandleValue dst) +{ + return listNode(AST_PROGRAM, "body", elts, pos, dst); +} + +bool +NodeBuilder::blockStatement(NodeVector& elts, TokenPos* pos, MutableHandleValue dst) +{ + return listNode(AST_BLOCK_STMT, "body", elts, pos, dst); +} + +bool +NodeBuilder::expressionStatement(HandleValue expr, TokenPos* pos, MutableHandleValue dst) +{ + RootedValue cb(cx, callbacks[AST_EXPR_STMT]); + if (!cb.isNull()) + return callback(cb, expr, pos, dst); + + return newNode(AST_EXPR_STMT, pos, "expression", expr, dst); +} + +bool +NodeBuilder::emptyStatement(TokenPos* pos, MutableHandleValue dst) +{ + RootedValue cb(cx, callbacks[AST_EMPTY_STMT]); + if (!cb.isNull()) + return callback(cb, pos, dst); + + return newNode(AST_EMPTY_STMT, pos, dst); +} + +bool +NodeBuilder::ifStatement(HandleValue test, HandleValue cons, HandleValue alt, TokenPos* pos, + MutableHandleValue dst) +{ + RootedValue cb(cx, callbacks[AST_IF_STMT]); + if (!cb.isNull()) + return callback(cb, test, cons, opt(alt), pos, dst); + + return newNode(AST_IF_STMT, pos, + "test", test, + "consequent", cons, + "alternate", alt, + dst); +} + +bool +NodeBuilder::breakStatement(HandleValue label, TokenPos* pos, MutableHandleValue dst) +{ + RootedValue cb(cx, callbacks[AST_BREAK_STMT]); + if (!cb.isNull()) + return callback(cb, opt(label), pos, dst); + + return newNode(AST_BREAK_STMT, pos, "label", label, dst); +} + +bool +NodeBuilder::continueStatement(HandleValue label, TokenPos* pos, MutableHandleValue dst) +{ + RootedValue cb(cx, callbacks[AST_CONTINUE_STMT]); + if (!cb.isNull()) + return callback(cb, opt(label), pos, dst); + + return newNode(AST_CONTINUE_STMT, pos, "label", label, dst); +} + +bool +NodeBuilder::labeledStatement(HandleValue label, HandleValue stmt, TokenPos* pos, + MutableHandleValue dst) +{ + RootedValue cb(cx, callbacks[AST_LAB_STMT]); + if (!cb.isNull()) + return callback(cb, label, stmt, pos, dst); + + return newNode(AST_LAB_STMT, pos, + "label", label, + "body", stmt, + dst); +} + +bool +NodeBuilder::throwStatement(HandleValue arg, TokenPos* pos, MutableHandleValue dst) +{ + RootedValue cb(cx, callbacks[AST_THROW_STMT]); + if (!cb.isNull()) + return callback(cb, arg, pos, dst); + + return newNode(AST_THROW_STMT, pos, "argument", arg, dst); +} + +bool +NodeBuilder::returnStatement(HandleValue arg, TokenPos* pos, MutableHandleValue dst) +{ + RootedValue cb(cx, callbacks[AST_RETURN_STMT]); + if (!cb.isNull()) + return callback(cb, opt(arg), pos, dst); + + return newNode(AST_RETURN_STMT, pos, "argument", arg, dst); +} + +bool +NodeBuilder::forStatement(HandleValue init, HandleValue test, HandleValue update, HandleValue stmt, + TokenPos* pos, MutableHandleValue dst) +{ + RootedValue cb(cx, callbacks[AST_FOR_STMT]); + if (!cb.isNull()) + return callback(cb, opt(init), opt(test), opt(update), stmt, pos, dst); + + return newNode(AST_FOR_STMT, pos, + "init", init, + "test", test, + "update", update, + "body", stmt, + dst); +} + +bool +NodeBuilder::forInStatement(HandleValue var, HandleValue expr, HandleValue stmt, bool isForEach, + TokenPos* pos, MutableHandleValue dst) +{ + RootedValue isForEachVal(cx, BooleanValue(isForEach)); + + RootedValue cb(cx, callbacks[AST_FOR_IN_STMT]); + if (!cb.isNull()) + return callback(cb, var, expr, stmt, isForEachVal, pos, dst); + + return newNode(AST_FOR_IN_STMT, pos, + "left", var, + "right", expr, + "body", stmt, + "each", isForEachVal, + dst); +} + +bool +NodeBuilder::forOfStatement(HandleValue var, HandleValue expr, HandleValue stmt, TokenPos* pos, + MutableHandleValue dst) +{ + RootedValue cb(cx, callbacks[AST_FOR_OF_STMT]); + if (!cb.isNull()) + return callback(cb, var, expr, stmt, pos, dst); + + return newNode(AST_FOR_OF_STMT, pos, + "left", var, + "right", expr, + "body", stmt, + dst); +} + +bool +NodeBuilder::withStatement(HandleValue expr, HandleValue stmt, TokenPos* pos, + MutableHandleValue dst) +{ + RootedValue cb(cx, callbacks[AST_WITH_STMT]); + if (!cb.isNull()) + return callback(cb, expr, stmt, pos, dst); + + return newNode(AST_WITH_STMT, pos, + "object", expr, + "body", stmt, + dst); +} + +bool +NodeBuilder::whileStatement(HandleValue test, HandleValue stmt, TokenPos* pos, + MutableHandleValue dst) +{ + RootedValue cb(cx, callbacks[AST_WHILE_STMT]); + if (!cb.isNull()) + return callback(cb, test, stmt, pos, dst); + + return newNode(AST_WHILE_STMT, pos, + "test", test, + "body", stmt, + dst); +} + +bool +NodeBuilder::doWhileStatement(HandleValue stmt, HandleValue test, TokenPos* pos, + MutableHandleValue dst) +{ + RootedValue cb(cx, callbacks[AST_DO_STMT]); + if (!cb.isNull()) + return callback(cb, stmt, test, pos, dst); + + return newNode(AST_DO_STMT, pos, + "body", stmt, + "test", test, + dst); +} + +bool +NodeBuilder::switchStatement(HandleValue disc, NodeVector& elts, bool lexical, TokenPos* pos, + MutableHandleValue dst) +{ + RootedValue array(cx); + if (!newArray(elts, &array)) + return false; + + RootedValue lexicalVal(cx, BooleanValue(lexical)); + + RootedValue cb(cx, callbacks[AST_SWITCH_STMT]); + if (!cb.isNull()) + return callback(cb, disc, array, lexicalVal, pos, dst); + + return newNode(AST_SWITCH_STMT, pos, + "discriminant", disc, + "cases", array, + "lexical", lexicalVal, + dst); +} + +bool +NodeBuilder::tryStatement(HandleValue body, NodeVector& guarded, HandleValue unguarded, + HandleValue finally, TokenPos* pos, MutableHandleValue dst) +{ + RootedValue guardedHandlers(cx); + if (!newArray(guarded, &guardedHandlers)) + return false; + + RootedValue cb(cx, callbacks[AST_TRY_STMT]); + if (!cb.isNull()) + return callback(cb, body, guardedHandlers, unguarded, opt(finally), pos, dst); + + return newNode(AST_TRY_STMT, pos, + "block", body, + "guardedHandlers", guardedHandlers, + "handler", unguarded, + "finalizer", finally, + dst); +} + +bool +NodeBuilder::debuggerStatement(TokenPos* pos, MutableHandleValue dst) +{ + RootedValue cb(cx, callbacks[AST_DEBUGGER_STMT]); + if (!cb.isNull()) + return callback(cb, pos, dst); + + return newNode(AST_DEBUGGER_STMT, pos, dst); +} + +bool +NodeBuilder::binaryExpression(BinaryOperator op, HandleValue left, HandleValue right, TokenPos* pos, + MutableHandleValue dst) +{ + MOZ_ASSERT(op > BINOP_ERR && op < BINOP_LIMIT); + + RootedValue opName(cx); + if (!atomValue(binopNames[op], &opName)) + return false; + + RootedValue cb(cx, callbacks[AST_BINARY_EXPR]); + if (!cb.isNull()) + return callback(cb, opName, left, right, pos, dst); + + return newNode(AST_BINARY_EXPR, pos, + "operator", opName, + "left", left, + "right", right, + dst); +} + +bool +NodeBuilder::unaryExpression(UnaryOperator unop, HandleValue expr, TokenPos* pos, + MutableHandleValue dst) +{ + MOZ_ASSERT(unop > UNOP_ERR && unop < UNOP_LIMIT); + + RootedValue opName(cx); + if (!atomValue(unopNames[unop], &opName)) + return false; + + RootedValue cb(cx, callbacks[AST_UNARY_EXPR]); + if (!cb.isNull()) + return callback(cb, opName, expr, pos, dst); + + RootedValue trueVal(cx, BooleanValue(true)); + return newNode(AST_UNARY_EXPR, pos, + "operator", opName, + "argument", expr, + "prefix", trueVal, + dst); +} + +bool +NodeBuilder::assignmentExpression(AssignmentOperator aop, HandleValue lhs, HandleValue rhs, + TokenPos* pos, MutableHandleValue dst) +{ + MOZ_ASSERT(aop > AOP_ERR && aop < AOP_LIMIT); + + RootedValue opName(cx); + if (!atomValue(aopNames[aop], &opName)) + return false; + + RootedValue cb(cx, callbacks[AST_ASSIGN_EXPR]); + if (!cb.isNull()) + return callback(cb, opName, lhs, rhs, pos, dst); + + return newNode(AST_ASSIGN_EXPR, pos, + "operator", opName, + "left", lhs, + "right", rhs, + dst); +} + +bool +NodeBuilder::updateExpression(HandleValue expr, bool incr, bool prefix, TokenPos* pos, + MutableHandleValue dst) +{ + RootedValue opName(cx); + if (!atomValue(incr ? "++" : "--", &opName)) + return false; + + RootedValue prefixVal(cx, BooleanValue(prefix)); + + RootedValue cb(cx, callbacks[AST_UPDATE_EXPR]); + if (!cb.isNull()) + return callback(cb, expr, opName, prefixVal, pos, dst); + + return newNode(AST_UPDATE_EXPR, pos, + "operator", opName, + "argument", expr, + "prefix", prefixVal, + dst); +} + +bool +NodeBuilder::logicalExpression(bool lor, HandleValue left, HandleValue right, TokenPos* pos, + MutableHandleValue dst) +{ + RootedValue opName(cx); + if (!atomValue(lor ? "||" : "&&", &opName)) + return false; + + RootedValue cb(cx, callbacks[AST_LOGICAL_EXPR]); + if (!cb.isNull()) + return callback(cb, opName, left, right, pos, dst); + + return newNode(AST_LOGICAL_EXPR, pos, + "operator", opName, + "left", left, + "right", right, + dst); +} + +bool +NodeBuilder::conditionalExpression(HandleValue test, HandleValue cons, HandleValue alt, + TokenPos* pos, MutableHandleValue dst) +{ + RootedValue cb(cx, callbacks[AST_COND_EXPR]); + if (!cb.isNull()) + return callback(cb, test, cons, alt, pos, dst); + + return newNode(AST_COND_EXPR, pos, + "test", test, + "consequent", cons, + "alternate", alt, + dst); +} + +bool +NodeBuilder::sequenceExpression(NodeVector& elts, TokenPos* pos, MutableHandleValue dst) +{ + return listNode(AST_LIST_EXPR, "expressions", elts, pos, dst); +} + +bool +NodeBuilder::callExpression(HandleValue callee, NodeVector& args, TokenPos* pos, + MutableHandleValue dst) +{ + RootedValue array(cx); + if (!newArray(args, &array)) + return false; + + RootedValue cb(cx, callbacks[AST_CALL_EXPR]); + if (!cb.isNull()) + return callback(cb, callee, array, pos, dst); + + return newNode(AST_CALL_EXPR, pos, + "callee", callee, + "arguments", array, + dst); +} + +bool +NodeBuilder::newExpression(HandleValue callee, NodeVector& args, TokenPos* pos, + MutableHandleValue dst) +{ + RootedValue array(cx); + if (!newArray(args, &array)) + return false; + + RootedValue cb(cx, callbacks[AST_NEW_EXPR]); + if (!cb.isNull()) + return callback(cb, callee, array, pos, dst); + + return newNode(AST_NEW_EXPR, pos, + "callee", callee, + "arguments", array, + dst); +} + +bool +NodeBuilder::memberExpression(bool computed, HandleValue expr, HandleValue member, TokenPos* pos, + MutableHandleValue dst) +{ + RootedValue computedVal(cx, BooleanValue(computed)); + + RootedValue cb(cx, callbacks[AST_MEMBER_EXPR]); + if (!cb.isNull()) + return callback(cb, computedVal, expr, member, pos, dst); + + return newNode(AST_MEMBER_EXPR, pos, + "object", expr, + "property", member, + "computed", computedVal, + dst); +} + +bool +NodeBuilder::arrayExpression(NodeVector& elts, TokenPos* pos, MutableHandleValue dst) +{ + return listNode(AST_ARRAY_EXPR, "elements", elts, pos, dst); +} + +bool +NodeBuilder::callSiteObj(NodeVector& raw, NodeVector& cooked, TokenPos* pos, MutableHandleValue dst) +{ + RootedValue rawVal(cx); + if (!newArray(raw, &rawVal)) + return false; + + RootedValue cookedVal(cx); + if (!newArray(cooked, &cookedVal)) + return false; + + return newNode(AST_CALL_SITE_OBJ, pos, + "raw", rawVal, + "cooked", cookedVal, + dst); +} + +bool +NodeBuilder::taggedTemplate(HandleValue callee, NodeVector& args, TokenPos* pos, + MutableHandleValue dst) +{ + RootedValue array(cx); + if (!newArray(args, &array)) + return false; + + return newNode(AST_TAGGED_TEMPLATE, pos, + "callee", callee, + "arguments", array, + dst); +} + +bool +NodeBuilder::templateLiteral(NodeVector& elts, TokenPos* pos, MutableHandleValue dst) +{ + return listNode(AST_TEMPLATE_LITERAL, "elements", elts, pos, dst); +} + +bool +NodeBuilder::computedName(HandleValue name, TokenPos* pos, MutableHandleValue dst) +{ + return newNode(AST_COMPUTED_NAME, pos, + "name", name, + dst); +} + +bool +NodeBuilder::spreadExpression(HandleValue expr, TokenPos* pos, MutableHandleValue dst) +{ + return newNode(AST_SPREAD_EXPR, pos, + "expression", expr, + dst); +} + +bool +NodeBuilder::propertyPattern(HandleValue key, HandleValue patt, bool isShorthand, TokenPos* pos, + MutableHandleValue dst) +{ + RootedValue kindName(cx); + if (!atomValue("init", &kindName)) + return false; + + RootedValue isShorthandVal(cx, BooleanValue(isShorthand)); + + RootedValue cb(cx, callbacks[AST_PROP_PATT]); + if (!cb.isNull()) + return callback(cb, key, patt, pos, dst); + + return newNode(AST_PROP_PATT, pos, + "key", key, + "value", patt, + "kind", kindName, + "shorthand", isShorthandVal, + dst); +} + +bool +NodeBuilder::prototypeMutation(HandleValue val, TokenPos* pos, MutableHandleValue dst) +{ + RootedValue cb(cx, callbacks[AST_PROTOTYPEMUTATION]); + if (!cb.isNull()) + return callback(cb, val, pos, dst); + + return newNode(AST_PROTOTYPEMUTATION, pos, + "value", val, + dst); +} + +bool +NodeBuilder::propertyInitializer(HandleValue key, HandleValue val, PropKind kind, bool isShorthand, + bool isMethod, TokenPos* pos, MutableHandleValue dst) +{ + RootedValue kindName(cx); + if (!atomValue(kind == PROP_INIT + ? "init" + : kind == PROP_GETTER + ? "get" + : "set", &kindName)) { + return false; + } + + RootedValue isShorthandVal(cx, BooleanValue(isShorthand)); + RootedValue isMethodVal(cx, BooleanValue(isMethod)); + + RootedValue cb(cx, callbacks[AST_PROPERTY]); + if (!cb.isNull()) + return callback(cb, kindName, key, val, pos, dst); + + return newNode(AST_PROPERTY, pos, + "key", key, + "value", val, + "kind", kindName, + "method", isMethodVal, + "shorthand", isShorthandVal, + dst); +} + +bool +NodeBuilder::objectExpression(NodeVector& elts, TokenPos* pos, MutableHandleValue dst) +{ + return listNode(AST_OBJECT_EXPR, "properties", elts, pos, dst); +} + +bool +NodeBuilder::thisExpression(TokenPos* pos, MutableHandleValue dst) +{ + RootedValue cb(cx, callbacks[AST_THIS_EXPR]); + if (!cb.isNull()) + return callback(cb, pos, dst); + + return newNode(AST_THIS_EXPR, pos, dst); +} + +bool +NodeBuilder::yieldExpression(HandleValue arg, YieldKind kind, TokenPos* pos, MutableHandleValue dst) +{ + RootedValue cb(cx, callbacks[AST_YIELD_EXPR]); + RootedValue delegateVal(cx); + + switch (kind) { + case Delegating: + delegateVal = BooleanValue(true); + break; + case NotDelegating: + delegateVal = BooleanValue(false); + break; + } + + if (!cb.isNull()) + return callback(cb, opt(arg), delegateVal, pos, dst); + return newNode(AST_YIELD_EXPR, pos, "argument", arg, "delegate", delegateVal, dst); +} + +bool +NodeBuilder::comprehensionBlock(HandleValue patt, HandleValue src, bool isForEach, bool isForOf, TokenPos* pos, + MutableHandleValue dst) +{ + RootedValue isForEachVal(cx, BooleanValue(isForEach)); + RootedValue isForOfVal(cx, BooleanValue(isForOf)); + + RootedValue cb(cx, callbacks[AST_COMP_BLOCK]); + if (!cb.isNull()) + return callback(cb, patt, src, isForEachVal, isForOfVal, pos, dst); + + return newNode(AST_COMP_BLOCK, pos, + "left", patt, + "right", src, + "each", isForEachVal, + "of", isForOfVal, + dst); +} + +bool +NodeBuilder::comprehensionIf(HandleValue test, TokenPos* pos, MutableHandleValue dst) +{ + RootedValue cb(cx, callbacks[AST_COMP_IF]); + if (!cb.isNull()) + return callback(cb, test, pos, dst); + + return newNode(AST_COMP_IF, pos, + "test", test, + dst); +} + +bool +NodeBuilder::comprehensionExpression(HandleValue body, NodeVector& blocks, HandleValue filter, + bool isLegacy, TokenPos* pos, MutableHandleValue dst) +{ + RootedValue array(cx); + if (!newArray(blocks, &array)) + return false; + + RootedValue style(cx); + if (!atomValue(isLegacy ? "legacy" : "modern", &style)) + return false; + + RootedValue cb(cx, callbacks[AST_COMP_EXPR]); + if (!cb.isNull()) + return callback(cb, body, array, opt(filter), style, pos, dst); + + return newNode(AST_COMP_EXPR, pos, + "body", body, + "blocks", array, + "filter", filter, + "style", style, + dst); +} + +bool +NodeBuilder::generatorExpression(HandleValue body, NodeVector& blocks, HandleValue filter, + bool isLegacy, TokenPos* pos, MutableHandleValue dst) +{ + RootedValue array(cx); + if (!newArray(blocks, &array)) + return false; + + RootedValue style(cx); + if (!atomValue(isLegacy ? "legacy" : "modern", &style)) + return false; + + RootedValue cb(cx, callbacks[AST_GENERATOR_EXPR]); + if (!cb.isNull()) + return callback(cb, body, array, opt(filter), style, pos, dst); + + return newNode(AST_GENERATOR_EXPR, pos, + "body", body, + "blocks", array, + "filter", filter, + "style", style, + dst); +} + +bool +NodeBuilder::importDeclaration(NodeVector& elts, HandleValue moduleSpec, TokenPos* pos, + MutableHandleValue dst) +{ + RootedValue array(cx); + if (!newArray(elts, &array)) + return false; + + RootedValue cb(cx, callbacks[AST_IMPORT_DECL]); + if (!cb.isNull()) + return callback(cb, array, moduleSpec, pos, dst); + + return newNode(AST_IMPORT_DECL, pos, + "specifiers", array, + "source", moduleSpec, + dst); +} + +bool +NodeBuilder::importSpecifier(HandleValue importName, HandleValue bindingName, TokenPos* pos, + MutableHandleValue dst) +{ + RootedValue cb(cx, callbacks[AST_IMPORT_SPEC]); + if (!cb.isNull()) + return callback(cb, importName, bindingName, pos, dst); + + return newNode(AST_IMPORT_SPEC, pos, + "id", importName, + "name", bindingName, + dst); +} + +bool +NodeBuilder::exportDeclaration(HandleValue decl, NodeVector& elts, HandleValue moduleSpec, + HandleValue isDefault, TokenPos* pos, MutableHandleValue dst) +{ + RootedValue array(cx, NullValue()); + if (decl.isNull() && !newArray(elts, &array)) + return false; + + RootedValue cb(cx, callbacks[AST_EXPORT_DECL]); + + if (!cb.isNull()) + return callback(cb, decl, array, moduleSpec, pos, dst); + + return newNode(AST_EXPORT_DECL, pos, + "declaration", decl, + "specifiers", array, + "source", moduleSpec, + "isDefault", isDefault, + dst); +} + +bool +NodeBuilder::exportSpecifier(HandleValue bindingName, HandleValue exportName, TokenPos* pos, + MutableHandleValue dst) +{ + RootedValue cb(cx, callbacks[AST_EXPORT_SPEC]); + if (!cb.isNull()) + return callback(cb, bindingName, exportName, pos, dst); + + return newNode(AST_EXPORT_SPEC, pos, + "id", bindingName, + "name", exportName, + dst); +} + +bool +NodeBuilder::exportBatchSpecifier(TokenPos* pos, MutableHandleValue dst) +{ + RootedValue cb(cx, callbacks[AST_EXPORT_BATCH_SPEC]); + if (!cb.isNull()) + return callback(cb, pos, dst); + + return newNode(AST_EXPORT_BATCH_SPEC, pos, dst); +} + +bool +NodeBuilder::variableDeclaration(NodeVector& elts, VarDeclKind kind, TokenPos* pos, + MutableHandleValue dst) +{ + MOZ_ASSERT(kind > VARDECL_ERR && kind < VARDECL_LIMIT); + + RootedValue array(cx), kindName(cx); + if (!newArray(elts, &array) || + !atomValue(kind == VARDECL_CONST + ? "const" + : kind == VARDECL_LET + ? "let" + : "var", &kindName)) { + return false; + } + + RootedValue cb(cx, callbacks[AST_VAR_DECL]); + if (!cb.isNull()) + return callback(cb, kindName, array, pos, dst); + + return newNode(AST_VAR_DECL, pos, + "kind", kindName, + "declarations", array, + dst); +} + +bool +NodeBuilder::variableDeclarator(HandleValue id, HandleValue init, TokenPos* pos, + MutableHandleValue dst) +{ + RootedValue cb(cx, callbacks[AST_VAR_DTOR]); + if (!cb.isNull()) + return callback(cb, id, opt(init), pos, dst); + + return newNode(AST_VAR_DTOR, pos, "id", id, "init", init, dst); +} + +bool +NodeBuilder::switchCase(HandleValue expr, NodeVector& elts, TokenPos* pos, MutableHandleValue dst) +{ + RootedValue array(cx); + if (!newArray(elts, &array)) + return false; + + RootedValue cb(cx, callbacks[AST_CASE]); + if (!cb.isNull()) + return callback(cb, opt(expr), array, pos, dst); + + return newNode(AST_CASE, pos, + "test", expr, + "consequent", array, + dst); +} + +bool +NodeBuilder::catchClause(HandleValue var, HandleValue guard, HandleValue body, TokenPos* pos, + MutableHandleValue dst) +{ + RootedValue cb(cx, callbacks[AST_CATCH]); + if (!cb.isNull()) + return callback(cb, var, opt(guard), body, pos, dst); + + return newNode(AST_CATCH, pos, + "param", var, + "guard", guard, + "body", body, + dst); +} + +bool +NodeBuilder::literal(HandleValue val, TokenPos* pos, MutableHandleValue dst) +{ + RootedValue cb(cx, callbacks[AST_LITERAL]); + if (!cb.isNull()) + return callback(cb, val, pos, dst); + + return newNode(AST_LITERAL, pos, "value", val, dst); +} + +bool +NodeBuilder::identifier(HandleValue name, TokenPos* pos, MutableHandleValue dst) +{ + RootedValue cb(cx, callbacks[AST_IDENTIFIER]); + if (!cb.isNull()) + return callback(cb, name, pos, dst); + + return newNode(AST_IDENTIFIER, pos, "name", name, dst); +} + +bool +NodeBuilder::objectPattern(NodeVector& elts, TokenPos* pos, MutableHandleValue dst) +{ + return listNode(AST_OBJECT_PATT, "properties", elts, pos, dst); +} + +bool +NodeBuilder::arrayPattern(NodeVector& elts, TokenPos* pos, MutableHandleValue dst) +{ + return listNode(AST_ARRAY_PATT, "elements", elts, pos, dst); +} + +bool +NodeBuilder::function(ASTType type, TokenPos* pos, + HandleValue id, NodeVector& args, NodeVector& defaults, + HandleValue body, HandleValue rest, + GeneratorStyle generatorStyle, bool isAsync, bool isExpression, + MutableHandleValue dst) +{ + RootedValue array(cx), defarray(cx); + if (!newArray(args, &array)) + return false; + if (!newArray(defaults, &defarray)) + return false; + + 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]); + if (!cb.isNull()) { + return callback(cb, opt(id), array, body, isGeneratorVal, isExpressionVal, pos, dst); + } + + if (isGenerator) { + // Distinguish ES6 generators from legacy generators. + RootedValue styleVal(cx); + JSAtom* styleStr = generatorStyle == GeneratorStyle::ES6 + ? Atomize(cx, "es6", 3) + : Atomize(cx, "legacy", 6); + if (!styleStr) + return false; + styleVal.setString(styleStr); + return newNode(type, pos, + "id", id, + "params", array, + "defaults", defarray, + "body", body, + "rest", rest, + "generator", isGeneratorVal, + "async", isAsyncVal, + "style", styleVal, + "expression", isExpressionVal, + dst); + } + + return newNode(type, pos, + "id", id, + "params", array, + "defaults", defarray, + "body", body, + "rest", rest, + "generator", isGeneratorVal, + "async", isAsyncVal, + "expression", isExpressionVal, + dst); +} + +bool +NodeBuilder::classMethod(HandleValue name, HandleValue body, PropKind kind, bool isStatic, + TokenPos* pos, MutableHandleValue dst) +{ + RootedValue kindName(cx); + if (!atomValue(kind == PROP_INIT + ? "method" + : kind == PROP_GETTER + ? "get" + : "set", &kindName)) { + return false; + } + + RootedValue isStaticVal(cx, BooleanValue(isStatic)); + RootedValue cb(cx, callbacks[AST_CLASS_METHOD]); + if (!cb.isNull()) + return callback(cb, kindName, name, body, isStaticVal, pos, dst); + + return newNode(AST_CLASS_METHOD, pos, + "name", name, + "body", body, + "kind", kindName, + "static", isStaticVal, + dst); +} + +bool +NodeBuilder::classMethods(NodeVector& methods, MutableHandleValue dst) +{ + return newArray(methods, dst); +} + +bool +NodeBuilder::classDefinition(bool expr, HandleValue name, HandleValue heritage, HandleValue block, + TokenPos* pos, MutableHandleValue dst) +{ + ASTType type = expr ? AST_CLASS_EXPR : AST_CLASS_STMT; + RootedValue cb(cx, callbacks[type]); + if (!cb.isNull()) + return callback(cb, name, heritage, block, pos, dst); + + return newNode(type, pos, + "id", name, + "superClass", heritage, + "body", block, + dst); +} + +bool +NodeBuilder::metaProperty(HandleValue meta, HandleValue property, TokenPos* pos, MutableHandleValue dst) +{ + RootedValue cb(cx, callbacks[AST_METAPROPERTY]); + if (!cb.isNull()) + return callback(cb, meta, property, pos, dst); + + return newNode(AST_METAPROPERTY, pos, + "meta", meta, + "property", property, + dst); +} + +bool +NodeBuilder::super(TokenPos* pos, MutableHandleValue dst) +{ + RootedValue cb(cx, callbacks[AST_SUPER]); + if (!cb.isNull()) + return callback(cb, pos, dst); + + return newNode(AST_SUPER, pos, dst); +} + +namespace { + +/* + * Serialization of parse nodes to JavaScript objects. + * + * All serialization methods take a non-nullable ParseNode pointer. + */ +class ASTSerializer +{ + JSContext* cx; + Parser<FullParseHandler>* parser; + NodeBuilder builder; + DebugOnly<uint32_t> lineno; + + Value unrootedAtomContents(JSAtom* atom) { + return StringValue(atom ? atom : cx->names().empty); + } + + BinaryOperator binop(ParseNodeKind kind, JSOp op); + UnaryOperator unop(ParseNodeKind kind, JSOp op); + AssignmentOperator aop(JSOp op); + + bool statements(ParseNode* pn, NodeVector& elts); + bool expressions(ParseNode* pn, NodeVector& elts); + bool leftAssociate(ParseNode* pn, MutableHandleValue dst); + bool rightAssociate(ParseNode* pn, MutableHandleValue dst); + bool functionArgs(ParseNode* pn, ParseNode* pnargs, + NodeVector& args, NodeVector& defaults, MutableHandleValue rest); + + bool sourceElement(ParseNode* pn, MutableHandleValue dst); + + bool declaration(ParseNode* pn, MutableHandleValue dst); + bool variableDeclaration(ParseNode* pn, bool lexical, MutableHandleValue dst); + bool variableDeclarator(ParseNode* pn, MutableHandleValue dst); + bool importDeclaration(ParseNode* pn, MutableHandleValue dst); + bool importSpecifier(ParseNode* pn, MutableHandleValue dst); + bool exportDeclaration(ParseNode* pn, MutableHandleValue dst); + bool exportSpecifier(ParseNode* pn, MutableHandleValue dst); + bool classDefinition(ParseNode* pn, bool expr, MutableHandleValue dst); + + bool optStatement(ParseNode* pn, MutableHandleValue dst) { + if (!pn) { + dst.setMagic(JS_SERIALIZE_NO_NODE); + return true; + } + return statement(pn, dst); + } + + bool forInit(ParseNode* pn, MutableHandleValue dst); + bool forIn(ParseNode* loop, ParseNode* head, HandleValue var, HandleValue stmt, + MutableHandleValue dst); + bool forOf(ParseNode* loop, ParseNode* head, HandleValue var, HandleValue stmt, + MutableHandleValue dst); + bool statement(ParseNode* pn, MutableHandleValue dst); + bool blockStatement(ParseNode* pn, MutableHandleValue dst); + bool switchStatement(ParseNode* pn, MutableHandleValue dst); + bool switchCase(ParseNode* pn, MutableHandleValue dst); + bool tryStatement(ParseNode* pn, MutableHandleValue dst); + bool catchClause(ParseNode* pn, bool* isGuarded, MutableHandleValue dst); + + bool optExpression(ParseNode* pn, MutableHandleValue dst) { + if (!pn) { + dst.setMagic(JS_SERIALIZE_NO_NODE); + return true; + } + return expression(pn, dst); + } + + bool expression(ParseNode* pn, MutableHandleValue dst); + + bool propertyName(ParseNode* pn, MutableHandleValue dst); + bool property(ParseNode* pn, MutableHandleValue dst); + + bool classMethod(ParseNode* pn, MutableHandleValue dst); + + bool optIdentifier(HandleAtom atom, TokenPos* pos, MutableHandleValue dst) { + if (!atom) { + dst.setMagic(JS_SERIALIZE_NO_NODE); + return true; + } + return identifier(atom, pos, dst); + } + + bool identifier(HandleAtom atom, TokenPos* pos, MutableHandleValue dst); + bool identifier(ParseNode* pn, MutableHandleValue dst); + bool literal(ParseNode* pn, MutableHandleValue dst); + + bool pattern(ParseNode* pn, MutableHandleValue dst); + bool arrayPattern(ParseNode* pn, MutableHandleValue dst); + bool objectPattern(ParseNode* pn, MutableHandleValue dst); + + 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); + + bool comprehensionBlock(ParseNode* pn, MutableHandleValue dst); + bool comprehensionIf(ParseNode* pn, MutableHandleValue dst); + bool comprehension(ParseNode* pn, MutableHandleValue dst); + bool generatorExpression(ParseNode* pn, MutableHandleValue dst); + + public: + ASTSerializer(JSContext* c, bool l, char const* src, uint32_t ln) + : cx(c) + , parser(nullptr) + , builder(c, l, src) +#ifdef DEBUG + , lineno(ln) +#endif + {} + + bool init(HandleObject userobj) { + return builder.init(userobj); + } + + void setParser(Parser<FullParseHandler>* p) { + parser = p; + builder.setTokenStream(&p->tokenStream); + } + + bool program(ParseNode* pn, MutableHandleValue dst); +}; + +} /* anonymous namespace */ + +AssignmentOperator +ASTSerializer::aop(JSOp op) +{ + switch (op) { + case JSOP_NOP: + return AOP_ASSIGN; + case JSOP_ADD: + return AOP_PLUS; + case JSOP_SUB: + return AOP_MINUS; + case JSOP_MUL: + return AOP_STAR; + case JSOP_DIV: + return AOP_DIV; + case JSOP_MOD: + return AOP_MOD; + case JSOP_POW: + return AOP_POW; + case JSOP_LSH: + return AOP_LSH; + case JSOP_RSH: + return AOP_RSH; + case JSOP_URSH: + return AOP_URSH; + case JSOP_BITOR: + return AOP_BITOR; + case JSOP_BITXOR: + return AOP_BITXOR; + case JSOP_BITAND: + return AOP_BITAND; + default: + return AOP_ERR; + } +} + +UnaryOperator +ASTSerializer::unop(ParseNodeKind kind, JSOp op) +{ + if (IsDeleteKind(kind)) + return UNOP_DELETE; + + if (IsTypeofKind(kind)) + return UNOP_TYPEOF; + + if (kind == PNK_AWAIT) + return UNOP_AWAIT; + + switch (op) { + case JSOP_NEG: + return UNOP_NEG; + case JSOP_POS: + return UNOP_POS; + case JSOP_NOT: + return UNOP_NOT; + case JSOP_BITNOT: + return UNOP_BITNOT; + case JSOP_VOID: + return UNOP_VOID; + default: + return UNOP_ERR; + } +} + +BinaryOperator +ASTSerializer::binop(ParseNodeKind kind, JSOp op) +{ + switch (kind) { + case PNK_LSH: + return BINOP_LSH; + case PNK_RSH: + return BINOP_RSH; + case PNK_URSH: + return BINOP_URSH; + case PNK_LT: + return BINOP_LT; + case PNK_LE: + return BINOP_LE; + case PNK_GT: + return BINOP_GT; + case PNK_GE: + return BINOP_GE; + case PNK_EQ: + return BINOP_EQ; + case PNK_NE: + return BINOP_NE; + case PNK_STRICTEQ: + return BINOP_STRICTEQ; + case PNK_STRICTNE: + return BINOP_STRICTNE; + case PNK_ADD: + return BINOP_ADD; + case PNK_SUB: + return BINOP_SUB; + case PNK_STAR: + return BINOP_STAR; + case PNK_DIV: + return BINOP_DIV; + case PNK_MOD: + return BINOP_MOD; + case PNK_POW: + return BINOP_POW; + case PNK_BITOR: + return BINOP_BITOR; + case PNK_BITXOR: + return BINOP_BITXOR; + case PNK_BITAND: + return BINOP_BITAND; + case PNK_IN: + return BINOP_IN; + case PNK_INSTANCEOF: + return BINOP_INSTANCEOF; + default: + return BINOP_ERR; + } +} + +bool +ASTSerializer::statements(ParseNode* pn, NodeVector& elts) +{ + MOZ_ASSERT(pn->isKind(PNK_STATEMENTLIST)); + MOZ_ASSERT(pn->isArity(PN_LIST)); + + if (!elts.reserve(pn->pn_count)) + return false; + + for (ParseNode* next = pn->pn_head; next; next = next->pn_next) { + MOZ_ASSERT(pn->pn_pos.encloses(next->pn_pos)); + + RootedValue elt(cx); + if (!sourceElement(next, &elt)) + return false; + elts.infallibleAppend(elt); + } + + return true; +} + +bool +ASTSerializer::expressions(ParseNode* pn, NodeVector& elts) +{ + if (!elts.reserve(pn->pn_count)) + return false; + + for (ParseNode* next = pn->pn_head; next; next = next->pn_next) { + MOZ_ASSERT(pn->pn_pos.encloses(next->pn_pos)); + + RootedValue elt(cx); + if (!expression(next, &elt)) + return false; + elts.infallibleAppend(elt); + } + + return true; +} + +bool +ASTSerializer::blockStatement(ParseNode* pn, MutableHandleValue dst) +{ + MOZ_ASSERT(pn->isKind(PNK_STATEMENTLIST)); + + NodeVector stmts(cx); + return statements(pn, stmts) && + builder.blockStatement(stmts, &pn->pn_pos, dst); +} + +bool +ASTSerializer::program(ParseNode* pn, MutableHandleValue dst) +{ + MOZ_ASSERT(parser->tokenStream.srcCoords.lineNum(pn->pn_pos.begin) == lineno); + + NodeVector stmts(cx); + return statements(pn, stmts) && + builder.program(stmts, &pn->pn_pos, dst); +} + +bool +ASTSerializer::sourceElement(ParseNode* pn, MutableHandleValue dst) +{ + /* SpiderMonkey allows declarations even in pure statement contexts. */ + return statement(pn, dst); +} + +bool +ASTSerializer::declaration(ParseNode* pn, MutableHandleValue dst) +{ + MOZ_ASSERT(pn->isKind(PNK_FUNCTION) || + pn->isKind(PNK_VAR) || + pn->isKind(PNK_LET) || + pn->isKind(PNK_CONST)); + + switch (pn->getKind()) { + case PNK_FUNCTION: + return function(pn, AST_FUNC_DECL, dst); + + case PNK_VAR: + return variableDeclaration(pn, false, dst); + + default: + MOZ_ASSERT(pn->isKind(PNK_LET) || pn->isKind(PNK_CONST)); + return variableDeclaration(pn, true, dst); + } +} + +bool +ASTSerializer::variableDeclaration(ParseNode* pn, bool lexical, MutableHandleValue dst) +{ + MOZ_ASSERT_IF(lexical, pn->isKind(PNK_LET) || pn->isKind(PNK_CONST)); + MOZ_ASSERT_IF(!lexical, pn->isKind(PNK_VAR)); + + VarDeclKind kind = VARDECL_ERR; + // Treat both the toplevel const binding (secretly var-like) and the lexical const + // the same way + if (lexical) + kind = pn->isKind(PNK_LET) ? VARDECL_LET : VARDECL_CONST; + else + kind = pn->isKind(PNK_VAR) ? VARDECL_VAR : VARDECL_CONST; + + NodeVector dtors(cx); + if (!dtors.reserve(pn->pn_count)) + return false; + for (ParseNode* next = pn->pn_head; next; next = next->pn_next) { + RootedValue child(cx); + if (!variableDeclarator(next, &child)) + return false; + dtors.infallibleAppend(child); + } + return builder.variableDeclaration(dtors, kind, &pn->pn_pos, dst); +} + +bool +ASTSerializer::variableDeclarator(ParseNode* pn, MutableHandleValue dst) +{ + ParseNode* pnleft; + ParseNode* pnright; + + if (pn->isKind(PNK_NAME)) { + pnleft = pn; + pnright = pn->pn_expr; + MOZ_ASSERT_IF(pnright, pn->pn_pos.encloses(pnright->pn_pos)); + } else if (pn->isKind(PNK_ASSIGN)) { + pnleft = pn->pn_left; + pnright = pn->pn_right; + MOZ_ASSERT(pn->pn_pos.encloses(pnleft->pn_pos)); + MOZ_ASSERT(pn->pn_pos.encloses(pnright->pn_pos)); + } else { + /* This happens for a destructuring declarator in a for-in/of loop. */ + pnleft = pn; + pnright = nullptr; + } + + RootedValue left(cx), right(cx); + return pattern(pnleft, &left) && + optExpression(pnright, &right) && + builder.variableDeclarator(left, right, &pn->pn_pos, dst); +} + +bool +ASTSerializer::importDeclaration(ParseNode* pn, MutableHandleValue dst) +{ + MOZ_ASSERT(pn->isKind(PNK_IMPORT)); + MOZ_ASSERT(pn->isArity(PN_BINARY)); + MOZ_ASSERT(pn->pn_left->isKind(PNK_IMPORT_SPEC_LIST)); + MOZ_ASSERT(pn->pn_right->isKind(PNK_STRING)); + + NodeVector elts(cx); + if (!elts.reserve(pn->pn_left->pn_count)) + return false; + + for (ParseNode* next = pn->pn_left->pn_head; next; next = next->pn_next) { + RootedValue elt(cx); + if (!importSpecifier(next, &elt)) + return false; + elts.infallibleAppend(elt); + } + + RootedValue moduleSpec(cx); + return literal(pn->pn_right, &moduleSpec) && + builder.importDeclaration(elts, moduleSpec, &pn->pn_pos, dst); +} + +bool +ASTSerializer::importSpecifier(ParseNode* pn, MutableHandleValue dst) +{ + MOZ_ASSERT(pn->isKind(PNK_IMPORT_SPEC)); + + RootedValue importName(cx); + RootedValue bindingName(cx); + return identifier(pn->pn_left, &importName) && + identifier(pn->pn_right, &bindingName) && + builder.importSpecifier(importName, bindingName, &pn->pn_pos, dst); +} + +bool +ASTSerializer::exportDeclaration(ParseNode* pn, MutableHandleValue dst) +{ + MOZ_ASSERT(pn->isKind(PNK_EXPORT) || + pn->isKind(PNK_EXPORT_FROM) || + pn->isKind(PNK_EXPORT_DEFAULT)); + MOZ_ASSERT(pn->getArity() == pn->isKind(PNK_EXPORT) ? PN_UNARY : PN_BINARY); + MOZ_ASSERT_IF(pn->isKind(PNK_EXPORT_FROM), pn->pn_right->isKind(PNK_STRING)); + + RootedValue decl(cx, NullValue()); + NodeVector elts(cx); + + ParseNode* kid = pn->isKind(PNK_EXPORT) ? pn->pn_kid : pn->pn_left; + switch (ParseNodeKind kind = kid->getKind()) { + case PNK_EXPORT_SPEC_LIST: + if (!elts.reserve(pn->pn_left->pn_count)) + return false; + + for (ParseNode* next = pn->pn_left->pn_head; next; next = next->pn_next) { + RootedValue elt(cx); + if (next->isKind(PNK_EXPORT_SPEC)) { + if (!exportSpecifier(next, &elt)) + return false; + } else { + if (!builder.exportBatchSpecifier(&pn->pn_pos, &elt)) + return false; + } + elts.infallibleAppend(elt); + } + break; + + case PNK_FUNCTION: + if (!function(kid, AST_FUNC_DECL, &decl)) + return false; + break; + + case PNK_CLASS: + if (!classDefinition(kid, false, &decl)) + return false; + break; + + case PNK_VAR: + case PNK_CONST: + case PNK_LET: + if (!variableDeclaration(kid, kind != PNK_VAR, &decl)) + return false; + break; + + default: + if (!expression(kid, &decl)) + return false; + break; + } + + RootedValue moduleSpec(cx, NullValue()); + if (pn->isKind(PNK_EXPORT_FROM) && !literal(pn->pn_right, &moduleSpec)) + return false; + + RootedValue isDefault(cx, BooleanValue(false)); + if (pn->isKind(PNK_EXPORT_DEFAULT)) + isDefault.setBoolean(true); + + return builder.exportDeclaration(decl, elts, moduleSpec, isDefault, &pn->pn_pos, dst); +} + +bool +ASTSerializer::exportSpecifier(ParseNode* pn, MutableHandleValue dst) +{ + MOZ_ASSERT(pn->isKind(PNK_EXPORT_SPEC)); + + RootedValue bindingName(cx); + RootedValue exportName(cx); + return identifier(pn->pn_left, &bindingName) && + identifier(pn->pn_right, &exportName) && + builder.exportSpecifier(bindingName, exportName, &pn->pn_pos, dst); +} + +bool +ASTSerializer::switchCase(ParseNode* pn, MutableHandleValue dst) +{ + MOZ_ASSERT_IF(pn->pn_left, pn->pn_pos.encloses(pn->pn_left->pn_pos)); + MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_right->pn_pos)); + + NodeVector stmts(cx); + + RootedValue expr(cx); + + return optExpression(pn->as<CaseClause>().caseExpression(), &expr) && + statements(pn->as<CaseClause>().statementList(), stmts) && + builder.switchCase(expr, stmts, &pn->pn_pos, dst); +} + +bool +ASTSerializer::switchStatement(ParseNode* pn, MutableHandleValue dst) +{ + MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_left->pn_pos)); + MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_right->pn_pos)); + + RootedValue disc(cx); + + if (!expression(pn->pn_left, &disc)) + return false; + + ParseNode* listNode; + bool lexical; + + if (pn->pn_right->isKind(PNK_LEXICALSCOPE)) { + listNode = pn->pn_right->pn_expr; + lexical = true; + } else { + listNode = pn->pn_right; + lexical = false; + } + + NodeVector cases(cx); + if (!cases.reserve(listNode->pn_count)) + return false; + + for (ParseNode* next = listNode->pn_head; next; next = next->pn_next) { + RootedValue child(cx); + if (!switchCase(next, &child)) + return false; + cases.infallibleAppend(child); + } + + return builder.switchStatement(disc, cases, lexical, &pn->pn_pos, dst); +} + +bool +ASTSerializer::catchClause(ParseNode* pn, bool* isGuarded, MutableHandleValue dst) +{ + MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_kid1->pn_pos)); + MOZ_ASSERT_IF(pn->pn_kid2, pn->pn_pos.encloses(pn->pn_kid2->pn_pos)); + MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_kid3->pn_pos)); + + RootedValue var(cx), guard(cx), body(cx); + + if (!pattern(pn->pn_kid1, &var) || + !optExpression(pn->pn_kid2, &guard)) { + return false; + } + + *isGuarded = !guard.isMagic(JS_SERIALIZE_NO_NODE); + + return statement(pn->pn_kid3, &body) && + builder.catchClause(var, guard, body, &pn->pn_pos, dst); +} + +bool +ASTSerializer::tryStatement(ParseNode* pn, MutableHandleValue dst) +{ + MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_kid1->pn_pos)); + MOZ_ASSERT_IF(pn->pn_kid2, pn->pn_pos.encloses(pn->pn_kid2->pn_pos)); + MOZ_ASSERT_IF(pn->pn_kid3, pn->pn_pos.encloses(pn->pn_kid3->pn_pos)); + + RootedValue body(cx); + if (!statement(pn->pn_kid1, &body)) + return false; + + NodeVector guarded(cx); + RootedValue unguarded(cx, NullValue()); + + if (ParseNode* catchList = pn->pn_kid2) { + if (!guarded.reserve(catchList->pn_count)) + return false; + + for (ParseNode* next = catchList->pn_head; next; next = next->pn_next) { + RootedValue clause(cx); + bool isGuarded; + if (!catchClause(next->pn_expr, &isGuarded, &clause)) + return false; + if (isGuarded) + guarded.infallibleAppend(clause); + else + unguarded = clause; + } + } + + RootedValue finally(cx); + return optStatement(pn->pn_kid3, &finally) && + builder.tryStatement(body, guarded, unguarded, finally, &pn->pn_pos, dst); +} + +bool +ASTSerializer::forInit(ParseNode* pn, MutableHandleValue dst) +{ + if (!pn) { + dst.setMagic(JS_SERIALIZE_NO_NODE); + return true; + } + + bool lexical = pn->isKind(PNK_LET) || pn->isKind(PNK_CONST); + return (lexical || pn->isKind(PNK_VAR)) + ? variableDeclaration(pn, lexical, dst) + : expression(pn, dst); +} + +bool +ASTSerializer::forOf(ParseNode* loop, ParseNode* head, HandleValue var, HandleValue stmt, + MutableHandleValue dst) +{ + RootedValue expr(cx); + + return expression(head->pn_kid3, &expr) && + builder.forOfStatement(var, expr, stmt, &loop->pn_pos, dst); +} + +bool +ASTSerializer::forIn(ParseNode* loop, ParseNode* head, HandleValue var, HandleValue stmt, + MutableHandleValue dst) +{ + RootedValue expr(cx); + bool isForEach = loop->pn_iflags & JSITER_FOREACH; + + return expression(head->pn_kid3, &expr) && + builder.forInStatement(var, expr, stmt, isForEach, &loop->pn_pos, dst); +} + +bool +ASTSerializer::classDefinition(ParseNode* pn, bool expr, MutableHandleValue dst) +{ + RootedValue className(cx, MagicValue(JS_SERIALIZE_NO_NODE)); + RootedValue heritage(cx); + RootedValue classBody(cx); + + if (pn->pn_kid1) { + if (!identifier(pn->pn_kid1->as<ClassNames>().innerBinding(), &className)) + return false; + } + + return optExpression(pn->pn_kid2, &heritage) && + statement(pn->pn_kid3, &classBody) && + builder.classDefinition(expr, className, heritage, classBody, &pn->pn_pos, dst); +} + +bool +ASTSerializer::statement(ParseNode* pn, MutableHandleValue dst) +{ + JS_CHECK_RECURSION(cx, return false); + switch (pn->getKind()) { + case PNK_FUNCTION: + case PNK_VAR: + return declaration(pn, dst); + + case PNK_LET: + case PNK_CONST: + return declaration(pn, dst); + + case PNK_IMPORT: + return importDeclaration(pn, dst); + + case PNK_EXPORT: + case PNK_EXPORT_DEFAULT: + case PNK_EXPORT_FROM: + return exportDeclaration(pn, dst); + + case PNK_SEMI: + if (pn->pn_kid) { + RootedValue expr(cx); + return expression(pn->pn_kid, &expr) && + builder.expressionStatement(expr, &pn->pn_pos, dst); + } + return builder.emptyStatement(&pn->pn_pos, dst); + + case PNK_LEXICALSCOPE: + pn = pn->pn_expr; + if (!pn->isKind(PNK_STATEMENTLIST)) + return statement(pn, dst); + MOZ_FALLTHROUGH; + + case PNK_STATEMENTLIST: + return blockStatement(pn, dst); + + case PNK_IF: + { + MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_kid1->pn_pos)); + MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_kid2->pn_pos)); + MOZ_ASSERT_IF(pn->pn_kid3, pn->pn_pos.encloses(pn->pn_kid3->pn_pos)); + + RootedValue test(cx), cons(cx), alt(cx); + + return expression(pn->pn_kid1, &test) && + statement(pn->pn_kid2, &cons) && + optStatement(pn->pn_kid3, &alt) && + builder.ifStatement(test, cons, alt, &pn->pn_pos, dst); + } + + case PNK_SWITCH: + return switchStatement(pn, dst); + + case PNK_TRY: + return tryStatement(pn, dst); + + case PNK_WITH: + case PNK_WHILE: + { + MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_left->pn_pos)); + MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_right->pn_pos)); + + RootedValue expr(cx), stmt(cx); + + return expression(pn->pn_left, &expr) && + statement(pn->pn_right, &stmt) && + (pn->isKind(PNK_WITH) + ? builder.withStatement(expr, stmt, &pn->pn_pos, dst) + : builder.whileStatement(expr, stmt, &pn->pn_pos, dst)); + } + + case PNK_DOWHILE: + { + MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_left->pn_pos)); + MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_right->pn_pos)); + + RootedValue stmt(cx), test(cx); + + return statement(pn->pn_left, &stmt) && + expression(pn->pn_right, &test) && + builder.doWhileStatement(stmt, test, &pn->pn_pos, dst); + } + + case PNK_FOR: + case PNK_COMPREHENSIONFOR: + { + MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_left->pn_pos)); + MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_right->pn_pos)); + + ParseNode* head = pn->pn_left; + + MOZ_ASSERT_IF(head->pn_kid1, head->pn_pos.encloses(head->pn_kid1->pn_pos)); + MOZ_ASSERT_IF(head->pn_kid2, head->pn_pos.encloses(head->pn_kid2->pn_pos)); + MOZ_ASSERT_IF(head->pn_kid3, head->pn_pos.encloses(head->pn_kid3->pn_pos)); + + RootedValue stmt(cx); + if (!statement(pn->pn_right, &stmt)) + return false; + + if (head->isKind(PNK_FORIN) || head->isKind(PNK_FOROF)) { + RootedValue var(cx); + if (head->pn_kid1->isKind(PNK_LEXICALSCOPE)) { + if (!variableDeclaration(head->pn_kid1->pn_expr, true, &var)) + return false; + } else if (!head->pn_kid1->isKind(PNK_VAR) && + !head->pn_kid1->isKind(PNK_LET) && + !head->pn_kid1->isKind(PNK_CONST)) + { + if (!pattern(head->pn_kid1, &var)) + return false; + } else { + if (!variableDeclaration(head->pn_kid1, + head->pn_kid1->isKind(PNK_LET) || + head->pn_kid1->isKind(PNK_CONST), + &var)) + { + return false; + } + } + if (head->isKind(PNK_FORIN)) + return forIn(pn, head, var, stmt, dst); + return forOf(pn, head, var, stmt, dst); + } + + RootedValue init(cx), test(cx), update(cx); + + return forInit(head->pn_kid1, &init) && + optExpression(head->pn_kid2, &test) && + optExpression(head->pn_kid3, &update) && + builder.forStatement(init, test, update, stmt, &pn->pn_pos, dst); + } + + case PNK_BREAK: + case PNK_CONTINUE: + { + RootedValue label(cx); + RootedAtom pnAtom(cx, pn->pn_atom); + return optIdentifier(pnAtom, nullptr, &label) && + (pn->isKind(PNK_BREAK) + ? builder.breakStatement(label, &pn->pn_pos, dst) + : builder.continueStatement(label, &pn->pn_pos, dst)); + } + + case PNK_LABEL: + { + MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_expr->pn_pos)); + + RootedValue label(cx), stmt(cx); + RootedAtom pnAtom(cx, pn->as<LabeledStatement>().label()); + return identifier(pnAtom, nullptr, &label) && + statement(pn->pn_expr, &stmt) && + builder.labeledStatement(label, stmt, &pn->pn_pos, dst); + } + + case PNK_THROW: + { + MOZ_ASSERT_IF(pn->pn_kid, pn->pn_pos.encloses(pn->pn_kid->pn_pos)); + + RootedValue arg(cx); + + return optExpression(pn->pn_kid, &arg) && + builder.throwStatement(arg, &pn->pn_pos, dst); + } + + case PNK_RETURN: + { + MOZ_ASSERT_IF(pn->pn_kid, pn->pn_pos.encloses(pn->pn_kid->pn_pos)); + + RootedValue arg(cx); + + return optExpression(pn->pn_kid, &arg) && + builder.returnStatement(arg, &pn->pn_pos, dst); + } + + case PNK_DEBUGGER: + return builder.debuggerStatement(&pn->pn_pos, dst); + + case PNK_CLASS: + return classDefinition(pn, false, dst); + + case PNK_CLASSMETHODLIST: + { + NodeVector methods(cx); + if (!methods.reserve(pn->pn_count)) + return false; + + for (ParseNode* next = pn->pn_head; next; next = next->pn_next) { + MOZ_ASSERT(pn->pn_pos.encloses(next->pn_pos)); + + RootedValue prop(cx); + if (!classMethod(next, &prop)) + return false; + methods.infallibleAppend(prop); + } + + return builder.classMethods(methods, dst); + } + + case PNK_NOP: + return builder.emptyStatement(&pn->pn_pos, dst); + + default: + LOCAL_NOT_REACHED("unexpected statement type"); + } +} + +bool +ASTSerializer::classMethod(ParseNode* pn, MutableHandleValue dst) +{ + PropKind kind; + switch (pn->getOp()) { + case JSOP_INITPROP: + kind = PROP_INIT; + break; + + case JSOP_INITPROP_GETTER: + kind = PROP_GETTER; + break; + + case JSOP_INITPROP_SETTER: + kind = PROP_SETTER; + break; + + default: + LOCAL_NOT_REACHED("unexpected object-literal property"); + } + + RootedValue key(cx), val(cx); + bool isStatic = pn->as<ClassMethod>().isStatic(); + return propertyName(pn->pn_left, &key) && + expression(pn->pn_right, &val) && + builder.classMethod(key, val, kind, isStatic, &pn->pn_pos, dst); +} + +bool +ASTSerializer::leftAssociate(ParseNode* pn, MutableHandleValue dst) +{ + MOZ_ASSERT(pn->isArity(PN_LIST)); + MOZ_ASSERT(pn->pn_count >= 1); + + ParseNodeKind kind = pn->getKind(); + bool lor = kind == PNK_OR; + bool logop = lor || (kind == PNK_AND); + + ParseNode* head = pn->pn_head; + RootedValue left(cx); + if (!expression(head, &left)) + return false; + for (ParseNode* next = head->pn_next; next; next = next->pn_next) { + RootedValue right(cx); + if (!expression(next, &right)) + return false; + + TokenPos subpos(pn->pn_pos.begin, next->pn_pos.end); + + if (logop) { + if (!builder.logicalExpression(lor, left, right, &subpos, &left)) + return false; + } else { + BinaryOperator op = binop(pn->getKind(), pn->getOp()); + LOCAL_ASSERT(op > BINOP_ERR && op < BINOP_LIMIT); + + if (!builder.binaryExpression(op, left, right, &subpos, &left)) + return false; + } + } + + dst.set(left); + return true; +} + +bool +ASTSerializer::rightAssociate(ParseNode* pn, MutableHandleValue dst) +{ + MOZ_ASSERT(pn->isArity(PN_LIST)); + MOZ_ASSERT(pn->pn_count >= 1); + + // First, we need to reverse the list, so that we can traverse it in the right order. + // It's OK to destructively reverse the list, because there are no other consumers. + + ParseNode* head = pn->pn_head; + ParseNode* prev = nullptr; + ParseNode* current = head; + ParseNode* next; + while (current != nullptr) { + next = current->pn_next; + current->pn_next = prev; + prev = current; + current = next; + } + + head = prev; + + RootedValue right(cx); + if (!expression(head, &right)) + return false; + for (ParseNode* next = head->pn_next; next; next = next->pn_next) { + RootedValue left(cx); + if (!expression(next, &left)) + return false; + + TokenPos subpos(pn->pn_pos.begin, next->pn_pos.end); + + BinaryOperator op = binop(pn->getKind(), pn->getOp()); + LOCAL_ASSERT(op > BINOP_ERR && op < BINOP_LIMIT); + + if (!builder.binaryExpression(op, left, right, &subpos, &right)) + return false; + } + + dst.set(right); + return true; +} + +bool +ASTSerializer::comprehensionBlock(ParseNode* pn, MutableHandleValue dst) +{ + LOCAL_ASSERT(pn->isArity(PN_BINARY)); + + ParseNode* in = pn->pn_left; + + LOCAL_ASSERT(in && (in->isKind(PNK_FORIN) || in->isKind(PNK_FOROF))); + + bool isForEach = in->isKind(PNK_FORIN) && (pn->pn_iflags & JSITER_FOREACH); + bool isForOf = in->isKind(PNK_FOROF); + + ParseNode* decl = in->pn_kid1; + if (decl->isKind(PNK_LEXICALSCOPE)) + decl = decl->pn_expr; + MOZ_ASSERT(decl->pn_count == 1); + + RootedValue patt(cx), src(cx); + return pattern(decl->pn_head, &patt) && + expression(in->pn_kid3, &src) && + builder.comprehensionBlock(patt, src, isForEach, isForOf, &in->pn_pos, dst); +} + +bool +ASTSerializer::comprehensionIf(ParseNode* pn, MutableHandleValue dst) +{ + LOCAL_ASSERT(pn->isKind(PNK_IF)); + LOCAL_ASSERT(!pn->pn_kid3); + + RootedValue patt(cx); + return pattern(pn->pn_kid1, &patt) && + builder.comprehensionIf(patt, &pn->pn_pos, dst); +} + +bool +ASTSerializer::comprehension(ParseNode* pn, MutableHandleValue dst) +{ + // There are two array comprehension flavors. + // 1. The kind that was in ES4 for a while: [z for (x in y)] + // 2. The kind that was in ES6 for a while: [for (x of y) z] + // They have slightly different parse trees and scoping. + bool isLegacy = pn->isKind(PNK_LEXICALSCOPE); + ParseNode* next = isLegacy ? pn->pn_expr : pn; + LOCAL_ASSERT(next->isKind(PNK_COMPREHENSIONFOR)); + + NodeVector blocks(cx); + RootedValue filter(cx, MagicValue(JS_SERIALIZE_NO_NODE)); + while (true) { + if (next->isKind(PNK_COMPREHENSIONFOR)) { + RootedValue block(cx); + if (!comprehensionBlock(next, &block) || !blocks.append(block)) + return false; + next = next->pn_right; + } else if (next->isKind(PNK_IF)) { + if (isLegacy) { + MOZ_ASSERT(filter.isMagic(JS_SERIALIZE_NO_NODE)); + if (!optExpression(next->pn_kid1, &filter)) + return false; + } else { + // ES7 comprehension can contain multiple ComprehensionIfs. + RootedValue compif(cx); + if (!comprehensionIf(next, &compif) || !blocks.append(compif)) + return false; + } + next = next->pn_kid2; + } else { + break; + } + } + + LOCAL_ASSERT(next->isKind(PNK_ARRAYPUSH)); + + RootedValue body(cx); + + return expression(next->pn_kid, &body) && + builder.comprehensionExpression(body, blocks, filter, isLegacy, &pn->pn_pos, dst); +} + +bool +ASTSerializer::generatorExpression(ParseNode* pn, MutableHandleValue dst) +{ + // Just as there are two kinds of array comprehension (see + // ASTSerializer::comprehension), there are legacy and modern generator + // expression. + bool isLegacy = pn->isKind(PNK_LEXICALSCOPE); + ParseNode* next = isLegacy ? pn->pn_expr : pn; + LOCAL_ASSERT(next->isKind(PNK_COMPREHENSIONFOR)); + + NodeVector blocks(cx); + RootedValue filter(cx, MagicValue(JS_SERIALIZE_NO_NODE)); + while (true) { + if (next->isKind(PNK_COMPREHENSIONFOR)) { + RootedValue block(cx); + if (!comprehensionBlock(next, &block) || !blocks.append(block)) + return false; + next = next->pn_right; + } else if (next->isKind(PNK_IF)) { + if (isLegacy) { + MOZ_ASSERT(filter.isMagic(JS_SERIALIZE_NO_NODE)); + if (!optExpression(next->pn_kid1, &filter)) + return false; + } else { + // ES7 comprehension can contain multiple ComprehensionIfs. + RootedValue compif(cx); + if (!comprehensionIf(next, &compif) || !blocks.append(compif)) + return false; + } + next = next->pn_kid2; + } else { + break; + } + } + + LOCAL_ASSERT(next->isKind(PNK_SEMI) && + next->pn_kid->isKind(PNK_YIELD) && + next->pn_kid->pn_left); + + RootedValue body(cx); + + return expression(next->pn_kid->pn_left, &body) && + builder.generatorExpression(body, blocks, filter, isLegacy, &pn->pn_pos, dst); +} + +bool +ASTSerializer::expression(ParseNode* pn, MutableHandleValue dst) +{ + JS_CHECK_RECURSION(cx, return false); + switch (pn->getKind()) { + case PNK_FUNCTION: + { + ASTType type = pn->pn_funbox->function()->isArrow() ? AST_ARROW_EXPR : AST_FUNC_EXPR; + return function(pn, type, dst); + } + + case PNK_COMMA: + { + NodeVector exprs(cx); + return expressions(pn, exprs) && + builder.sequenceExpression(exprs, &pn->pn_pos, dst); + } + + case PNK_CONDITIONAL: + { + MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_kid1->pn_pos)); + MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_kid2->pn_pos)); + MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_kid3->pn_pos)); + + RootedValue test(cx), cons(cx), alt(cx); + + return expression(pn->pn_kid1, &test) && + expression(pn->pn_kid2, &cons) && + expression(pn->pn_kid3, &alt) && + builder.conditionalExpression(test, cons, alt, &pn->pn_pos, dst); + } + + case PNK_OR: + case PNK_AND: + return leftAssociate(pn, dst); + + case PNK_PREINCREMENT: + case PNK_PREDECREMENT: + { + MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_kid->pn_pos)); + + bool inc = pn->isKind(PNK_PREINCREMENT); + RootedValue expr(cx); + return expression(pn->pn_kid, &expr) && + builder.updateExpression(expr, inc, true, &pn->pn_pos, dst); + } + + case PNK_POSTINCREMENT: + case PNK_POSTDECREMENT: + { + MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_kid->pn_pos)); + + bool inc = pn->isKind(PNK_POSTINCREMENT); + RootedValue expr(cx); + return expression(pn->pn_kid, &expr) && + builder.updateExpression(expr, inc, false, &pn->pn_pos, dst); + } + + case PNK_ASSIGN: + case PNK_ADDASSIGN: + case PNK_SUBASSIGN: + case PNK_BITORASSIGN: + case PNK_BITXORASSIGN: + case PNK_BITANDASSIGN: + case PNK_LSHASSIGN: + case PNK_RSHASSIGN: + case PNK_URSHASSIGN: + case PNK_MULASSIGN: + case PNK_DIVASSIGN: + case PNK_MODASSIGN: + case PNK_POWASSIGN: + { + MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_left->pn_pos)); + MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_right->pn_pos)); + + AssignmentOperator op = aop(pn->getOp()); + LOCAL_ASSERT(op > AOP_ERR && op < AOP_LIMIT); + + RootedValue lhs(cx), rhs(cx); + return pattern(pn->pn_left, &lhs) && + expression(pn->pn_right, &rhs) && + builder.assignmentExpression(op, lhs, rhs, &pn->pn_pos, dst); + } + + case PNK_ADD: + case PNK_SUB: + case PNK_STRICTEQ: + case PNK_EQ: + case PNK_STRICTNE: + case PNK_NE: + case PNK_LT: + case PNK_LE: + case PNK_GT: + case PNK_GE: + case PNK_LSH: + case PNK_RSH: + case PNK_URSH: + case PNK_STAR: + case PNK_DIV: + case PNK_MOD: + case PNK_BITOR: + case PNK_BITXOR: + case PNK_BITAND: + case PNK_IN: + case PNK_INSTANCEOF: + return leftAssociate(pn, dst); + + case PNK_POW: + return rightAssociate(pn, dst); + + case PNK_DELETENAME: + case PNK_DELETEPROP: + case PNK_DELETEELEM: + case PNK_DELETEEXPR: + case PNK_TYPEOFNAME: + case PNK_TYPEOFEXPR: + case PNK_VOID: + 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)); + + UnaryOperator op = unop(pn->getKind(), pn->getOp()); + LOCAL_ASSERT(op > UNOP_ERR && op < UNOP_LIMIT); + + RootedValue expr(cx); + return expression(pn->pn_kid, &expr) && + builder.unaryExpression(op, expr, &pn->pn_pos, dst); + } + + case PNK_GENEXP: + return generatorExpression(pn->generatorExpr(), dst); + + case PNK_NEW: + case PNK_TAGGED_TEMPLATE: + case PNK_CALL: + case PNK_SUPERCALL: + { + ParseNode* next = pn->pn_head; + MOZ_ASSERT(pn->pn_pos.encloses(next->pn_pos)); + + RootedValue callee(cx); + if (pn->isKind(PNK_SUPERCALL)) { + MOZ_ASSERT(next->isKind(PNK_SUPERBASE)); + if (!builder.super(&next->pn_pos, &callee)) + return false; + } else { + if (!expression(next, &callee)) + return false; + } + + NodeVector args(cx); + if (!args.reserve(pn->pn_count - 1)) + return false; + + for (next = next->pn_next; next; next = next->pn_next) { + MOZ_ASSERT(pn->pn_pos.encloses(next->pn_pos)); + + RootedValue arg(cx); + if (!expression(next, &arg)) + return false; + args.infallibleAppend(arg); + } + + if (pn->getKind() == PNK_TAGGED_TEMPLATE) + return builder.taggedTemplate(callee, args, &pn->pn_pos, dst); + + // SUPERCALL is Call(super, args) + return pn->isKind(PNK_NEW) + ? builder.newExpression(callee, args, &pn->pn_pos, dst) + + : builder.callExpression(callee, args, &pn->pn_pos, dst); + } + + case PNK_DOT: + { + MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_expr->pn_pos)); + + RootedValue expr(cx); + RootedValue propname(cx); + RootedAtom pnAtom(cx, pn->pn_atom); + + if (pn->as<PropertyAccess>().isSuper()) { + if (!builder.super(&pn->pn_expr->pn_pos, &expr)) + return false; + } else { + if (!expression(pn->pn_expr, &expr)) + return false; + } + + return identifier(pnAtom, nullptr, &propname) && + builder.memberExpression(false, expr, propname, &pn->pn_pos, dst); + } + + case PNK_ELEM: + { + MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_left->pn_pos)); + MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_right->pn_pos)); + + RootedValue left(cx), right(cx); + + if (pn->as<PropertyByValue>().isSuper()) { + if (!builder.super(&pn->pn_left->pn_pos, &left)) + return false; + } else { + if (!expression(pn->pn_left, &left)) + return false; + } + + return expression(pn->pn_right, &right) && + builder.memberExpression(true, left, right, &pn->pn_pos, dst); + } + + case PNK_CALLSITEOBJ: + { + NodeVector raw(cx); + if (!raw.reserve(pn->pn_head->pn_count)) + return false; + for (ParseNode* next = pn->pn_head->pn_head; next; next = next->pn_next) { + MOZ_ASSERT(pn->pn_pos.encloses(next->pn_pos)); + + RootedValue expr(cx); + expr.setString(next->pn_atom); + raw.infallibleAppend(expr); + } + + NodeVector cooked(cx); + if (!cooked.reserve(pn->pn_count - 1)) + return false; + + for (ParseNode* next = pn->pn_head->pn_next; next; next = next->pn_next) { + MOZ_ASSERT(pn->pn_pos.encloses(next->pn_pos)); + + RootedValue expr(cx); + expr.setString(next->pn_atom); + cooked.infallibleAppend(expr); + } + + return builder.callSiteObj(raw, cooked, &pn->pn_pos, dst); + } + + case PNK_ARRAY: + { + NodeVector elts(cx); + if (!elts.reserve(pn->pn_count)) + return false; + + for (ParseNode* next = pn->pn_head; next; next = next->pn_next) { + MOZ_ASSERT(pn->pn_pos.encloses(next->pn_pos)); + + if (next->isKind(PNK_ELISION)) { + elts.infallibleAppend(NullValue()); + } else { + RootedValue expr(cx); + if (!expression(next, &expr)) + return false; + elts.infallibleAppend(expr); + } + } + + return builder.arrayExpression(elts, &pn->pn_pos, dst); + } + + case PNK_SPREAD: + { + RootedValue expr(cx); + return expression(pn->pn_kid, &expr) && + builder.spreadExpression(expr, &pn->pn_pos, dst); + } + + case PNK_COMPUTED_NAME: + { + RootedValue name(cx); + return expression(pn->pn_kid, &name) && + builder.computedName(name, &pn->pn_pos, dst); + } + + case PNK_OBJECT: + { + NodeVector elts(cx); + if (!elts.reserve(pn->pn_count)) + return false; + + for (ParseNode* next = pn->pn_head; next; next = next->pn_next) { + MOZ_ASSERT(pn->pn_pos.encloses(next->pn_pos)); + + RootedValue prop(cx); + if (!property(next, &prop)) + return false; + elts.infallibleAppend(prop); + } + + return builder.objectExpression(elts, &pn->pn_pos, dst); + } + + case PNK_NAME: + return identifier(pn, dst); + + case PNK_THIS: + return builder.thisExpression(&pn->pn_pos, dst); + + case PNK_TEMPLATE_STRING_LIST: + { + NodeVector elts(cx); + if (!elts.reserve(pn->pn_count)) + return false; + + for (ParseNode* next = pn->pn_head; next; next = next->pn_next) { + MOZ_ASSERT(pn->pn_pos.encloses(next->pn_pos)); + + RootedValue expr(cx); + if (!expression(next, &expr)) + return false; + elts.infallibleAppend(expr); + } + + return builder.templateLiteral(elts, &pn->pn_pos, dst); + } + + case PNK_TEMPLATE_STRING: + case PNK_STRING: + case PNK_REGEXP: + case PNK_NUMBER: + case PNK_TRUE: + case PNK_FALSE: + case PNK_NULL: + return literal(pn, dst); + + case PNK_YIELD_STAR: + { + MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_left->pn_pos)); + + RootedValue arg(cx); + return expression(pn->pn_left, &arg) && + builder.yieldExpression(arg, Delegating, &pn->pn_pos, dst); + } + + case PNK_YIELD: + { + MOZ_ASSERT_IF(pn->pn_left, pn->pn_pos.encloses(pn->pn_left->pn_pos)); + + RootedValue arg(cx); + return optExpression(pn->pn_left, &arg) && + builder.yieldExpression(arg, NotDelegating, &pn->pn_pos, dst); + } + + case PNK_ARRAYCOMP: + MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_head->pn_pos)); + + /* NB: it's no longer the case that pn_count could be 2. */ + LOCAL_ASSERT(pn->pn_count == 1); + return comprehension(pn->pn_head, dst); + + case PNK_CLASS: + return classDefinition(pn, true, dst); + + case PNK_NEWTARGET: + { + MOZ_ASSERT(pn->pn_left->isKind(PNK_POSHOLDER)); + MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_left->pn_pos)); + MOZ_ASSERT(pn->pn_right->isKind(PNK_POSHOLDER)); + MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_right->pn_pos)); + + RootedValue newIdent(cx); + RootedValue targetIdent(cx); + + RootedAtom newStr(cx, cx->names().new_); + RootedAtom targetStr(cx, cx->names().target); + + return identifier(newStr, &pn->pn_left->pn_pos, &newIdent) && + identifier(targetStr, &pn->pn_right->pn_pos, &targetIdent) && + builder.metaProperty(newIdent, targetIdent, &pn->pn_pos, dst); + } + + case PNK_SETTHIS: + // SETTHIS is used to assign the result of a super() call to |this|. + // It's not part of the original AST, so just forward to the call. + MOZ_ASSERT(pn->pn_left->isKind(PNK_NAME)); + return expression(pn->pn_right, dst); + + default: + LOCAL_NOT_REACHED("unexpected expression type"); + } +} + +bool +ASTSerializer::propertyName(ParseNode* pn, MutableHandleValue dst) +{ + if (pn->isKind(PNK_COMPUTED_NAME)) + return expression(pn, dst); + if (pn->isKind(PNK_OBJECT_PROPERTY_NAME)) + return identifier(pn, dst); + + LOCAL_ASSERT(pn->isKind(PNK_STRING) || pn->isKind(PNK_NUMBER)); + + return literal(pn, dst); +} + +bool +ASTSerializer::property(ParseNode* pn, MutableHandleValue dst) +{ + if (pn->isKind(PNK_MUTATEPROTO)) { + RootedValue val(cx); + return expression(pn->pn_kid, &val) && + builder.prototypeMutation(val, &pn->pn_pos, dst); + } + + PropKind kind; + switch (pn->getOp()) { + case JSOP_INITPROP: + kind = PROP_INIT; + break; + + case JSOP_INITPROP_GETTER: + kind = PROP_GETTER; + break; + + case JSOP_INITPROP_SETTER: + kind = PROP_SETTER; + break; + + default: + LOCAL_NOT_REACHED("unexpected object-literal property"); + } + + bool isShorthand = pn->isKind(PNK_SHORTHAND); + bool isMethod = + pn->pn_right->isKind(PNK_FUNCTION) && + pn->pn_right->pn_funbox->function()->kind() == JSFunction::Method; + RootedValue key(cx), val(cx); + return propertyName(pn->pn_left, &key) && + expression(pn->pn_right, &val) && + builder.propertyInitializer(key, val, kind, isShorthand, isMethod, &pn->pn_pos, dst); +} + +bool +ASTSerializer::literal(ParseNode* pn, MutableHandleValue dst) +{ + RootedValue val(cx); + switch (pn->getKind()) { + case PNK_TEMPLATE_STRING: + case PNK_STRING: + val.setString(pn->pn_atom); + break; + + case PNK_REGEXP: + { + RootedObject re1(cx, pn->as<RegExpLiteral>().objbox()->object); + LOCAL_ASSERT(re1 && re1->is<RegExpObject>()); + + RootedObject re2(cx, CloneRegExpObject(cx, re1)); + if (!re2) + return false; + + val.setObject(*re2); + break; + } + + case PNK_NUMBER: + val.setNumber(pn->pn_dval); + break; + + case PNK_NULL: + val.setNull(); + break; + + case PNK_TRUE: + val.setBoolean(true); + break; + + case PNK_FALSE: + val.setBoolean(false); + break; + + default: + LOCAL_NOT_REACHED("unexpected literal type"); + } + + return builder.literal(val, &pn->pn_pos, dst); +} + +bool +ASTSerializer::arrayPattern(ParseNode* pn, MutableHandleValue dst) +{ + MOZ_ASSERT(pn->isKind(PNK_ARRAY)); + + NodeVector elts(cx); + if (!elts.reserve(pn->pn_count)) + return false; + + for (ParseNode* next = pn->pn_head; next; next = next->pn_next) { + if (next->isKind(PNK_ELISION)) { + elts.infallibleAppend(NullValue()); + } else if (next->isKind(PNK_SPREAD)) { + RootedValue target(cx); + RootedValue spread(cx); + if (!pattern(next->pn_kid, &target)) + return false; + if(!builder.spreadExpression(target, &next->pn_pos, &spread)) + return false; + elts.infallibleAppend(spread); + } else { + RootedValue patt(cx); + if (!pattern(next, &patt)) + return false; + elts.infallibleAppend(patt); + } + } + + return builder.arrayPattern(elts, &pn->pn_pos, dst); +} + +bool +ASTSerializer::objectPattern(ParseNode* pn, MutableHandleValue dst) +{ + MOZ_ASSERT(pn->isKind(PNK_OBJECT)); + + NodeVector elts(cx); + if (!elts.reserve(pn->pn_count)) + return false; + + for (ParseNode* propdef = pn->pn_head; propdef; propdef = propdef->pn_next) { + LOCAL_ASSERT(propdef->isKind(PNK_MUTATEPROTO) != propdef->isOp(JSOP_INITPROP)); + + RootedValue key(cx); + ParseNode* target; + if (propdef->isKind(PNK_MUTATEPROTO)) { + RootedValue pname(cx, StringValue(cx->names().proto)); + if (!builder.literal(pname, &propdef->pn_pos, &key)) + return false; + target = propdef->pn_kid; + } else { + if (!propertyName(propdef->pn_left, &key)) + return false; + target = propdef->pn_right; + } + + RootedValue patt(cx), prop(cx); + if (!pattern(target, &patt) || + !builder.propertyPattern(key, patt, propdef->isKind(PNK_SHORTHAND), &propdef->pn_pos, + &prop)) + { + return false; + } + + elts.infallibleAppend(prop); + } + + return builder.objectPattern(elts, &pn->pn_pos, dst); +} + +bool +ASTSerializer::pattern(ParseNode* pn, MutableHandleValue dst) +{ + JS_CHECK_RECURSION(cx, return false); + switch (pn->getKind()) { + case PNK_OBJECT: + return objectPattern(pn, dst); + + case PNK_ARRAY: + return arrayPattern(pn, dst); + + default: + return expression(pn, dst); + } +} + +bool +ASTSerializer::identifier(HandleAtom atom, TokenPos* pos, MutableHandleValue dst) +{ + RootedValue atomContentsVal(cx, unrootedAtomContents(atom)); + return builder.identifier(atomContentsVal, pos, dst); +} + +bool +ASTSerializer::identifier(ParseNode* pn, MutableHandleValue dst) +{ + LOCAL_ASSERT(pn->isArity(PN_NAME) || pn->isArity(PN_NULLARY)); + LOCAL_ASSERT(pn->pn_atom); + + RootedAtom pnAtom(cx, pn->pn_atom); + return identifier(pnAtom, &pn->pn_pos, dst); +} + +bool +ASTSerializer::function(ParseNode* pn, ASTType type, MutableHandleValue dst) +{ + RootedFunction func(cx, pn->pn_funbox->function()); + + GeneratorStyle generatorStyle = + pn->pn_funbox->isGenerator() + ? (pn->pn_funbox->isLegacyGenerator() + ? GeneratorStyle::Legacy + : GeneratorStyle::ES6) + : GeneratorStyle::None; + + bool isAsync = pn->pn_funbox->isAsync(); + bool isExpression = +#if JS_HAS_EXPR_CLOSURES + func->isExprBody(); +#else + false; +#endif + + RootedValue id(cx); + RootedAtom funcAtom(cx, func->name()); + if (!optIdentifier(funcAtom, nullptr, &id)) + return false; + + NodeVector args(cx); + NodeVector defaults(cx); + + RootedValue body(cx), rest(cx); + if (func->hasRest()) + rest.setUndefined(); + else + rest.setNull(); + return functionArgsAndBody(pn->pn_body, args, defaults, isAsync, isExpression, &body, &rest) && + builder.function(type, &pn->pn_pos, id, args, defaults, body, + rest, generatorStyle, isAsync, isExpression, dst); +} + +bool +ASTSerializer::functionArgsAndBody(ParseNode* pn, NodeVector& args, NodeVector& defaults, + bool isAsync, bool isExpression, + MutableHandleValue body, MutableHandleValue rest) +{ + ParseNode* pnargs; + ParseNode* pnbody; + + /* Extract the args and body separately. */ + if (pn->isKind(PNK_PARAMSBODY)) { + pnargs = pn; + pnbody = pn->last(); + } else { + pnargs = nullptr; + pnbody = pn; + } + + if (pnbody->isKind(PNK_LEXICALSCOPE)) + pnbody = pnbody->scopeBody(); + + /* Serialize the arguments and body. */ + switch (pnbody->getKind()) { + case PNK_RETURN: /* expression closure, no destructured args */ + return functionArgs(pn, pnargs, args, defaults, rest) && + expression(pnbody->pn_kid, body); + + case PNK_STATEMENTLIST: /* statement closure */ + { + ParseNode* pnstart = pnbody->pn_head; + + // Skip over initial yield in generator. + if (pnstart && pnstart->isKind(PNK_YIELD)) { + MOZ_ASSERT(pnstart->getOp() == JSOP_INITIALYIELD); + 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, args, defaults, rest) && + expression(pnstart->pn_kid, body); + } + + return functionArgs(pn, pnargs, args, defaults, rest) && + functionBody(pnstart, &pnbody->pn_pos, body); + } + + default: + LOCAL_NOT_REACHED("unexpected function contents"); + } +} + +bool +ASTSerializer::functionArgs(ParseNode* pn, ParseNode* pnargs, + NodeVector& args, NodeVector& defaults, + MutableHandleValue rest) +{ + if (!pnargs) + return true; + + RootedValue node(cx); + bool defaultsNull = true; + MOZ_ASSERT(defaults.empty(), + "must be initially empty for it to be proper to clear this " + "when there are no defaults"); + + for (ParseNode* arg = pnargs->pn_head; arg && arg != pnargs->last(); arg = arg->pn_next) { + ParseNode* pat; + ParseNode* defNode; + if (arg->isKind(PNK_NAME) || arg->isKind(PNK_ARRAY) || arg->isKind(PNK_OBJECT)) { + pat = arg; + defNode = nullptr; + } else { + MOZ_ASSERT(arg->isKind(PNK_ASSIGN)); + pat = arg->pn_left; + defNode = arg->pn_right; + } + + // Process the name or pattern. + MOZ_ASSERT(pat->isKind(PNK_NAME) || pat->isKind(PNK_ARRAY) || pat->isKind(PNK_OBJECT)); + if (!pattern(pat, &node)) + return false; + if (rest.isUndefined() && arg->pn_next == pnargs->last()) { + rest.setObject(node.toObject()); + } else { + if (!args.append(node)) + return false; + } + + // Process its default (or lack thereof). + if (defNode) { + defaultsNull = false; + RootedValue def(cx); + if (!expression(defNode, &def) || !defaults.append(def)) + return false; + } else { + if (!defaults.append(NullValue())) + return false; + } + } + MOZ_ASSERT(!rest.isUndefined(), + "if a rest argument was present (signified by " + "|rest.isUndefined()| initially), the rest node was properly " + "recorded"); + + if (defaultsNull) + defaults.clear(); + + return true; +} + +bool +ASTSerializer::functionBody(ParseNode* pn, TokenPos* pos, MutableHandleValue dst) +{ + NodeVector elts(cx); + + /* We aren't sure how many elements there are up front, so we'll check each append. */ + for (ParseNode* next = pn; next; next = next->pn_next) { + RootedValue child(cx); + if (!sourceElement(next, &child) || !elts.append(child)) + return false; + } + + return builder.blockStatement(elts, pos, dst); +} + +static bool +reflect_parse(JSContext* cx, uint32_t argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + if (args.length() < 1) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED, + "Reflect.parse", "0", "s"); + return false; + } + + RootedString src(cx, ToString<CanGC>(cx, args[0])); + if (!src) + return false; + + ScopedJSFreePtr<char> filename; + uint32_t lineno = 1; + bool loc = true; + RootedObject builder(cx); + ParseTarget target = ParseTarget::Script; + + RootedValue arg(cx, args.get(1)); + + if (!arg.isNullOrUndefined()) { + if (!arg.isObject()) { + ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_UNEXPECTED_TYPE, + JSDVG_SEARCH_STACK, arg, nullptr, + "not an object", nullptr); + return false; + } + + RootedObject config(cx, &arg.toObject()); + + RootedValue prop(cx); + + /* config.loc */ + RootedId locId(cx, NameToId(cx->names().loc)); + RootedValue trueVal(cx, BooleanValue(true)); + if (!GetPropertyDefault(cx, config, locId, trueVal, &prop)) + return false; + + loc = ToBoolean(prop); + + if (loc) { + /* config.source */ + RootedId sourceId(cx, NameToId(cx->names().source)); + RootedValue nullVal(cx, NullValue()); + if (!GetPropertyDefault(cx, config, sourceId, nullVal, &prop)) + return false; + + if (!prop.isNullOrUndefined()) { + RootedString str(cx, ToString<CanGC>(cx, prop)); + if (!str) + return false; + + filename = JS_EncodeString(cx, str); + if (!filename) + return false; + } + + /* config.line */ + RootedId lineId(cx, NameToId(cx->names().line)); + RootedValue oneValue(cx, Int32Value(1)); + if (!GetPropertyDefault(cx, config, lineId, oneValue, &prop) || + !ToUint32(cx, prop, &lineno)) { + return false; + } + } + + /* config.builder */ + RootedId builderId(cx, NameToId(cx->names().builder)); + RootedValue nullVal(cx, NullValue()); + if (!GetPropertyDefault(cx, config, builderId, nullVal, &prop)) + return false; + + if (!prop.isNullOrUndefined()) { + if (!prop.isObject()) { + ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_UNEXPECTED_TYPE, + JSDVG_SEARCH_STACK, prop, nullptr, + "not an object", nullptr); + return false; + } + builder = &prop.toObject(); + } + + /* config.target */ + RootedId targetId(cx, NameToId(cx->names().target)); + RootedValue scriptVal(cx, StringValue(cx->names().script)); + if (!GetPropertyDefault(cx, config, targetId, scriptVal, &prop)) + return false; + + if (!prop.isString()) { + ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_UNEXPECTED_TYPE, JSDVG_SEARCH_STACK, + prop, nullptr, "not 'script' or 'module'", nullptr); + return false; + } + + RootedString stringProp(cx, prop.toString()); + bool isScript = false; + bool isModule = false; + if (!EqualStrings(cx, stringProp, cx->names().script, &isScript)) + return false; + + if (!EqualStrings(cx, stringProp, cx->names().module, &isModule)) + return false; + + if (isScript) { + target = ParseTarget::Script; + } else if (isModule) { + target = ParseTarget::Module; + } else { + JS_ReportErrorASCII(cx, "Bad target value, expected 'script' or 'module'"); + return false; + } + } + + /* Extract the builder methods first to report errors before parsing. */ + ASTSerializer serialize(cx, loc, filename, lineno); + if (!serialize.init(builder)) + return false; + + JSLinearString* linear = src->ensureLinear(cx); + if (!linear) + return false; + + AutoStableStringChars linearChars(cx); + if (!linearChars.initTwoByte(cx, linear)) + return false; + + CompileOptions options(cx); + options.setFileAndLine(filename, lineno); + options.setCanLazilyParse(false); + mozilla::Range<const char16_t> chars = linearChars.twoByteRange(); + UsedNameTracker usedNames(cx); + if (!usedNames.init()) + return false; + Parser<FullParseHandler> parser(cx, cx->tempLifoAlloc(), options, chars.begin().get(), + chars.length(), /* foldConstants = */ false, usedNames, + nullptr, nullptr); + if (!parser.checkOptions()) + return false; + + serialize.setParser(&parser); + + ParseNode* pn; + if (target == ParseTarget::Script) { + pn = parser.parse(); + if (!pn) + return false; + } else { + if (!GlobalObject::ensureModulePrototypesCreated(cx, cx->global())) + return false; + + Rooted<ModuleObject*> module(cx, ModuleObject::create(cx)); + if (!module) + return false; + + ModuleBuilder builder(cx, module); + ModuleSharedContext modulesc(cx, module, &cx->global()->emptyGlobalScope(), builder); + pn = parser.moduleBody(&modulesc); + if (!pn) + return false; + + MOZ_ASSERT(pn->getKind() == PNK_MODULE); + pn = pn->pn_body; + } + + RootedValue val(cx); + if (!serialize.program(pn, &val)) { + args.rval().setNull(); + return false; + } + + args.rval().set(val); + return true; +} + +JS_PUBLIC_API(bool) +JS_InitReflectParse(JSContext* cx, HandleObject global) +{ + RootedValue reflectVal(cx); + if (!GetProperty(cx, global, global, cx->names().Reflect, &reflectVal)) + return false; + if (!reflectVal.isObject()) { + JS_ReportErrorASCII(cx, "JS_InitReflectParse must be called during global initialization"); + return false; + } + + RootedObject reflectObj(cx, &reflectVal.toObject()); + return JS_DefineFunction(cx, reflectObj, "parse", reflect_parse, 1, 0); +} |