| /* -*- 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_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 */ |
| }; |
| |
| 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_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_BAD_PARSE_NODE); \ |
| return false; \ |
| } \ |
| JS_END_MACRO |
| |
| #define LOCAL_NOT_REACHED(expr) \ |
| JS_BEGIN_MACRO \ |
| MOZ_ASSERT(false); \ |
| JS_ReportErrorNumber(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) |
| {} |
| |
| 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: |
| template <size_t N> |
| bool callbackHelper(HandleValue fun, AutoValueArray<N>& 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) or args. |
| MOZ_ASSERT(i == N - 1); |
| if (saveLoc) { |
| RootedValue loc(cx); |
| if (!newNodeLoc(pos, &loc)) |
| return false; |
| args[i++].set(loc); |
| } |
| return Invoke(cx, userv, fun, N, args.begin(), dst); |
| } |
| |
| // Helper function for callback(). Note that all Arguments must be types |
| // that convert to HandleValue, so this is not really as template-y as it |
| // seems, just variadic. |
| template <size_t N, typename... Arguments> |
| bool callbackHelper(HandleValue fun, AutoValueArray<N>& args, size_t i, |
| HandleValue head, Arguments&&... tail) |
| { |
| // Recursive loop to store the arguments in the array. 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> |
| bool callback(HandleValue fun, Arguments&&... args) { |
| AutoValueArray<sizeof...(args) - 1> argv(cx); |
| return callbackHelper(fun, argv, 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; |
| } |
| |
| 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; |
| } |
| |
| bool newObject(MutableHandleObject dst) { |
| RootedPlainObject nobj(cx, NewBuiltinClassInstance<PlainObject>(cx)); |
| if (!nobj) |
| return false; |
| |
| dst.set(nobj); |
| return true; |
| } |
| |
| bool newArray(NodeVector& elts, MutableHandleValue dst); |
| |
| bool createNode(ASTType type, TokenPos* pos, MutableHandleObject dst); |
| |
| bool newNodeHelper(HandleObject obj, MutableHandleValue dst) { |
| // The end of the implementation of newNode(). |
| MOZ_ASSERT(obj); |
| dst.setObject(*obj); |
| return true; |
| } |
| |
| template <typename... Arguments> |
| 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> |
| bool newNode(ASTType type, TokenPos* pos, Arguments&&... args) { |
| RootedObject node(cx); |
| return createNode(type, pos, &node) && |
| newNodeHelper(node, Forward<Arguments>(args)...); |
| } |
| |
| 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); |
| } |
| |
| 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); |
| } |
| |
| bool newNodeLoc(TokenPos* pos, MutableHandleValue dst); |
| |
| 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 |
| */ |
| |
| bool program(NodeVector& elts, TokenPos* pos, MutableHandleValue dst); |
| |
| bool literal(HandleValue val, TokenPos* pos, MutableHandleValue dst); |
| |
| bool identifier(HandleValue name, TokenPos* pos, MutableHandleValue dst); |
| |
| bool function(ASTType type, TokenPos* pos, |
| HandleValue id, NodeVector& args, NodeVector& defaults, |
| HandleValue body, HandleValue rest, GeneratorStyle generatorStyle, |
| bool isExpression, MutableHandleValue dst); |
| |
| bool variableDeclarator(HandleValue id, HandleValue init, TokenPos* pos, |
| MutableHandleValue dst); |
| |
| bool switchCase(HandleValue expr, NodeVector& elts, TokenPos* pos, MutableHandleValue dst); |
| |
| bool catchClause(HandleValue var, HandleValue guard, HandleValue body, TokenPos* pos, |
| MutableHandleValue dst); |
| |
| bool prototypeMutation(HandleValue val, TokenPos* pos, MutableHandleValue dst); |
| bool propertyInitializer(HandleValue key, HandleValue val, PropKind kind, bool isShorthand, |
| bool isMethod, TokenPos* pos, MutableHandleValue dst); |
| |
| |
| /* |
| * statements |
| */ |
| |
| bool blockStatement(NodeVector& elts, TokenPos* pos, MutableHandleValue dst); |
| |
| bool expressionStatement(HandleValue expr, TokenPos* pos, MutableHandleValue dst); |
| |
| bool emptyStatement(TokenPos* pos, MutableHandleValue dst); |
| |
| bool ifStatement(HandleValue test, HandleValue cons, HandleValue alt, TokenPos* pos, |
| MutableHandleValue dst); |
| |
| bool breakStatement(HandleValue label, TokenPos* pos, MutableHandleValue dst); |
| |
| bool continueStatement(HandleValue label, TokenPos* pos, MutableHandleValue dst); |
| |
| bool labeledStatement(HandleValue label, HandleValue stmt, TokenPos* pos, |
| MutableHandleValue dst); |
| |
| bool throwStatement(HandleValue arg, TokenPos* pos, MutableHandleValue dst); |
| |
| bool returnStatement(HandleValue arg, TokenPos* pos, MutableHandleValue dst); |
| |
| bool forStatement(HandleValue init, HandleValue test, HandleValue update, HandleValue stmt, |
| TokenPos* pos, MutableHandleValue dst); |
| |
| bool forInStatement(HandleValue var, HandleValue expr, HandleValue stmt, |
| bool isForEach, TokenPos* pos, MutableHandleValue dst); |
| |
| bool forOfStatement(HandleValue var, HandleValue expr, HandleValue stmt, TokenPos* pos, |
| MutableHandleValue dst); |
| |
| bool withStatement(HandleValue expr, HandleValue stmt, TokenPos* pos, MutableHandleValue dst); |
| |
| bool whileStatement(HandleValue test, HandleValue stmt, TokenPos* pos, MutableHandleValue dst); |
| |
| bool doWhileStatement(HandleValue stmt, HandleValue test, TokenPos* pos, |
| MutableHandleValue dst); |
| |
| bool switchStatement(HandleValue disc, NodeVector& elts, bool lexical, TokenPos* pos, |
| MutableHandleValue dst); |
| |
| bool tryStatement(HandleValue body, NodeVector& guarded, HandleValue unguarded, |
| HandleValue finally, TokenPos* pos, MutableHandleValue dst); |
| |
| bool debuggerStatement(TokenPos* pos, MutableHandleValue dst); |
| |
| bool letStatement(NodeVector& head, HandleValue stmt, TokenPos* pos, MutableHandleValue dst); |
| |
| bool importDeclaration(NodeVector& elts, HandleValue moduleSpec, TokenPos* pos, MutableHandleValue dst); |
| |
| bool importSpecifier(HandleValue importName, HandleValue bindingName, TokenPos* pos, MutableHandleValue dst); |
| |
| bool exportDeclaration(HandleValue decl, NodeVector& elts, HandleValue moduleSpec, |
| HandleValue isDefault, TokenPos* pos, MutableHandleValue dst); |
| |
| bool exportSpecifier(HandleValue bindingName, HandleValue exportName, TokenPos* pos, MutableHandleValue dst); |
| |
| bool exportBatchSpecifier(TokenPos* pos, MutableHandleValue dst); |
| |
| bool classDefinition(bool expr, HandleValue name, HandleValue heritage, HandleValue block, TokenPos* pos, |
| MutableHandleValue dst); |
| bool classMethods(NodeVector& methods, MutableHandleValue dst); |
| bool classMethod(HandleValue name, HandleValue body, PropKind kind, bool isStatic, TokenPos* pos, MutableHandleValue dst); |
| |
| /* |
| * expressions |
| */ |
| |
| bool binaryExpression(BinaryOperator op, HandleValue left, HandleValue right, TokenPos* pos, |
| MutableHandleValue dst); |
| |
| bool unaryExpression(UnaryOperator op, HandleValue expr, TokenPos* pos, MutableHandleValue dst); |
| |
| bool assignmentExpression(AssignmentOperator op, HandleValue lhs, HandleValue rhs, |
| TokenPos* pos, MutableHandleValue dst); |
| |
| bool updateExpression(HandleValue expr, bool incr, bool prefix, TokenPos* pos, |
| MutableHandleValue dst); |
| |
| bool logicalExpression(bool lor, HandleValue left, HandleValue right, TokenPos* pos, |
| MutableHandleValue dst); |
| |
| bool conditionalExpression(HandleValue test, HandleValue cons, HandleValue alt, TokenPos* pos, |
| MutableHandleValue dst); |
| |
| bool sequenceExpression(NodeVector& elts, TokenPos* pos, MutableHandleValue dst); |
| |
| bool newExpression(HandleValue callee, NodeVector& args, TokenPos* pos, MutableHandleValue dst); |
| |
| bool callExpression(HandleValue callee, NodeVector& args, TokenPos* pos, |
| MutableHandleValue dst); |
| |
| bool memberExpression(bool computed, HandleValue expr, HandleValue member, TokenPos* pos, |
| MutableHandleValue dst); |
| |
| bool arrayExpression(NodeVector& elts, TokenPos* pos, MutableHandleValue dst); |
| |
| bool templateLiteral(NodeVector& elts, TokenPos* pos, MutableHandleValue dst); |
| |
| bool taggedTemplate(HandleValue callee, NodeVector& args, TokenPos* pos, |
| MutableHandleValue dst); |
| |
| bool callSiteObj(NodeVector& raw, NodeVector& cooked, TokenPos* pos, MutableHandleValue dst); |
| |
| bool spreadExpression(HandleValue expr, TokenPos* pos, MutableHandleValue dst); |
| |
| bool computedName(HandleValue name, TokenPos* pos, MutableHandleValue dst); |
| |
| bool objectExpression(NodeVector& elts, TokenPos* pos, MutableHandleValue dst); |
| |
| bool thisExpression(TokenPos* pos, MutableHandleValue dst); |
| |
| bool yieldExpression(HandleValue arg, YieldKind kind, TokenPos* pos, MutableHandleValue dst); |
| |
| bool comprehensionBlock(HandleValue patt, HandleValue src, bool isForEach, bool isForOf, TokenPos* pos, |
| MutableHandleValue dst); |
| bool comprehensionIf(HandleValue test, TokenPos* pos, MutableHandleValue dst); |
| |
| bool comprehensionExpression(HandleValue body, NodeVector& blocks, HandleValue filter, |
| bool isLegacy, TokenPos* pos, MutableHandleValue dst); |
| |
| bool generatorExpression(HandleValue body, NodeVector& blocks, HandleValue filter, |
| bool isLegacy, TokenPos* pos, MutableHandleValue dst); |
| |
| bool metaProperty(HandleValue meta, HandleValue property, TokenPos* pos, MutableHandleValue dst); |
| |
| bool super(TokenPos* pos, MutableHandleValue dst); |
| |
| /* |
| * declarations |
| */ |
| |
| bool variableDeclaration(NodeVector& elts, VarDeclKind kind, TokenPos* pos, |
| MutableHandleValue dst); |
| |
| /* |
| * patterns |
| */ |
| |
| bool arrayPattern(NodeVector& elts, TokenPos* pos, MutableHandleValue dst); |
| |
| bool objectPattern(NodeVector& elts, TokenPos* pos, MutableHandleValue dst); |
| |
| 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()); |
| defineProperty(node, "loc", nullVal); |
| return true; |
| } |
| |
| 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::letStatement(NodeVector& head, HandleValue stmt, TokenPos* pos, MutableHandleValue dst) |
| { |
| RootedValue array(cx); |
| if (!newArray(head, &array)) |
| return false; |
| |
| RootedValue cb(cx, callbacks[AST_LET_STMT]); |
| if (!cb.isNull()) |
| return callback(cb, array, stmt, pos, dst); |
| |
| return newNode(AST_LET_STMT, pos, |
| "head", array, |
| "body", stmt, |
| 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 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 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, |
| "style", styleVal, |
| "expression", isExpressionVal, |
| dst); |
| } |
| |
| return newNode(type, pos, |
| "id", id, |
| "params", array, |
| "defaults", defarray, |
| "body", body, |
| "rest", rest, |
| "generator", isGeneratorVal, |
| "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, ParseNode* pnbody, |
| 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 letBlock(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 objectPropertyName(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, |
| 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) |
| , 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 (kind == PNK_TYPEOFNAME || kind == PNK_TYPEOFEXPR) |
| return UNOP_TYPEOF; |
| |
| 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->isUsed() ? nullptr : 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::letBlock(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)); |
| |
| ParseNode* letHead = pn->pn_left; |
| LOCAL_ASSERT(letHead->isArity(PN_LIST)); |
| |
| ParseNode* letBody = pn->pn_right; |
| LOCAL_ASSERT(letBody->isKind(PNK_LEXICALSCOPE)); |
| |
| NodeVector dtors(cx); |
| if (!dtors.reserve(letHead->pn_count)) |
| return false; |
| |
| for (ParseNode* next = letHead->pn_head; next; next = next->pn_next) { |
| RootedValue child(cx); |
| |
| if (!variableDeclarator(next, &child)) |
| return false; |
| dtors.infallibleAppend(child); |
| } |
| |
| RootedValue v(cx); |
| return statement(letBody->pn_expr, &v) && |
| builder.letStatement(dtors, v, &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_LETBLOCK: |
| return letBlock(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_NAME: |
| LOCAL_ASSERT(pn->isUsed()); |
| return statement(pn->pn_lexdef, 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); |
| /* FALL THROUGH */ |
| |
| 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) { |
| if (!pattern(head->pn_kid2, &var)) |
| return false; |
| } else if (head->pn_kid1->isKind(PNK_LEXICALSCOPE)) { |
| if (!variableDeclaration(head->pn_kid1->pn_expr, true, &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); |
| |
| RootedValue patt(cx), src(cx); |
| return pattern(in->pn_kid2, &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_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); |
| } |
| |
| #if JS_HAS_GENERATOR_EXPRS |
| case PNK_GENEXP: |
| return generatorExpression(pn->generatorExpr(), dst); |
| #endif |
| |
| 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::objectPropertyName(ParseNode* pn, MutableHandleValue dst) |
| { |
| LOCAL_ASSERT(pn->isKind(PNK_OBJECT_PROPERTY_NAME)); |
| LOCAL_ASSERT(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 isExpression = |
| #if JS_HAS_EXPR_CLOSURES |
| func->isExprBody(); |
| #else |
| false; |
| #endif |
| |
| RootedValue id(cx); |
| RootedAtom funcAtom(cx, func->atom()); |
| 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, &body, &rest) && |
| builder.function(type, &pn->pn_pos, id, args, defaults, body, |
| rest, generatorStyle, isExpression, dst); |
| } |
| |
| bool |
| ASTSerializer::functionArgsAndBody(ParseNode* pn, NodeVector& args, NodeVector& defaults, |
| MutableHandleValue body, MutableHandleValue rest) |
| { |
| ParseNode* pnargs; |
| ParseNode* pnbody; |
| |
| /* Extract the args and body separately. */ |
| if (pn->isKind(PNK_ARGSBODY)) { |
| pnargs = pn; |
| pnbody = pn->last(); |
| } else { |
| pnargs = nullptr; |
| pnbody = pn; |
| } |
| |
| /* Serialize the arguments and body. */ |
| switch (pnbody->getKind()) { |
| case PNK_RETURN: /* expression closure, no destructured args */ |
| return functionArgs(pn, pnargs, pnbody, 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; |
| } |
| |
| return functionArgs(pn, pnargs, pnbody, args, defaults, rest) && |
| functionBody(pnstart, &pnbody->pn_pos, body); |
| } |
| |
| default: |
| LOCAL_NOT_REACHED("unexpected function contents"); |
| } |
| } |
| |
| bool |
| ASTSerializer::functionArgs(ParseNode* pn, ParseNode* pnargs, |
| ParseNode* pnbody, 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 != pnbody; arg = arg->pn_next) { |
| MOZ_ASSERT(arg->isKind(PNK_NAME) || arg->isKind(PNK_ASSIGN)); |
| ParseNode* argName = nullptr; |
| ParseNode* defNode = nullptr; |
| if (arg->isKind(PNK_ASSIGN)) { |
| argName = arg->pn_left; |
| defNode = arg->pn_right; |
| } else if (arg->pn_atom == cx->names().empty) { |
| ParseNode* destruct = arg->expr(); |
| if (destruct->isKind(PNK_ASSIGN)) { |
| defNode = destruct->pn_right; |
| destruct = destruct->pn_left; |
| } |
| if (!pattern(destruct, &node) || !args.append(node)) |
| return false; |
| } else { |
| argName = arg; |
| } |
| if (argName) { |
| if (!identifier(argName, &node)) |
| return false; |
| if (rest.isUndefined() && arg->pn_next == pnbody) |
| rest.setObject(node.toObject()); |
| else if (!args.append(node)) |
| return false; |
| } |
| 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 (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_ReportErrorNumber(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_ReportError(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(); |
| Parser<FullParseHandler> parser(cx, &cx->tempLifoAlloc(), options, chars.start().get(), |
| chars.length(), /* foldConstants = */ false, nullptr, nullptr); |
| if (!parser.checkOptions()) |
| return false; |
| |
| serialize.setParser(&parser); |
| |
| ParseNode* pn; |
| if (target == ParseTarget::Script) { |
| pn = parser.parse(); |
| if (!pn) |
| return false; |
| } else { |
| Rooted<ModuleObject*> module(cx, ModuleObject::create(cx, nullptr)); |
| if (!module) |
| return false; |
| |
| pn = parser.standaloneModule(module); |
| 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_ReportError(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); |
| } |