| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
| * vim: set ts=8 sts=4 et sw=4 tw=99: |
| * This Source Code Form is subject to the terms of the Mozilla Public |
| * License, v. 2.0. If a copy of the MPL was not distributed with this |
| * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
| |
| #ifndef frontend_ParseNode_h |
| #define frontend_ParseNode_h |
| |
| #include "mozilla/Attributes.h" |
| |
| #include "frontend/TokenStream.h" |
| |
| namespace js { |
| namespace frontend { |
| |
| template <typename ParseHandler> |
| struct ParseContext; |
| |
| class FullParseHandler; |
| class FunctionBox; |
| class ModuleBox; |
| class ObjectBox; |
| |
| // A packed ScopeCoordinate for use in the frontend during bytecode |
| // compilation. |
| // |
| // Definitions start out !isFree() && isHopsUnknown(). |
| // Uses start out isFree(). |
| // |
| // The BCE computes the correct number of hops based on the static scope |
| // chain. This is ncessary because due to hoisting, the Parser does not know |
| // the final static scope chain. |
| // |
| // The BCE also computes the correct slot number depending on whether the |
| // binding is aliased. If it is aliased, the slot number is the slot on the |
| // dynamic scope object. Otherwise, the slot number is the frame slot. |
| class PackedScopeCoordinate |
| { |
| uint32_t hops_ : SCOPECOORD_HOPS_BITS; |
| uint32_t slot_ : SCOPECOORD_SLOT_BITS; |
| |
| void checkInvariants() { |
| static_assert(sizeof(PackedScopeCoordinate) == sizeof(uint32_t), |
| "Not necessary for correctness, but good for ParseNode memory use"); |
| } |
| |
| public: |
| // Steal one value to represent the sentinel value signaling that the |
| // binding is free, and one value to represent the sentinel value |
| // signaling that the number of hop count need to be computed by the |
| // BytecodeEmitter. |
| static const uint32_t UNKNOWN_HOPS = SCOPECOORD_HOPS_LIMIT - 1; |
| static const uint32_t UNKNOWN_SLOT = SCOPECOORD_SLOT_LIMIT - 1; |
| bool isHopsUnknown() const { return hops_ == UNKNOWN_HOPS; } |
| bool isFree() const { return slot_ == UNKNOWN_SLOT; } |
| |
| uint32_t hops() const { MOZ_ASSERT(!isFree()); return hops_; } |
| uint32_t slot() const { MOZ_ASSERT(!isFree()); return slot_; } |
| |
| bool setSlot(TokenStream& ts, uint32_t newSlot) { |
| if (newSlot >= UNKNOWN_SLOT) |
| return ts.reportError(JSMSG_TOO_MANY_LOCALS); |
| slot_ = newSlot; |
| return true; |
| } |
| |
| bool setHops(TokenStream& ts, uint32_t newHops) { |
| if (newHops >= UNKNOWN_HOPS) |
| return ts.reportError(JSMSG_TOO_DEEP, js_function_str); |
| hops_ = newHops; |
| return true; |
| } |
| |
| bool set(TokenStream& ts, uint32_t newHops, uint32_t newSlot) { |
| return setHops(ts, newHops) && setSlot(ts, newSlot); |
| } |
| |
| void makeFree() { |
| hops_ = UNKNOWN_HOPS; |
| slot_ = UNKNOWN_SLOT; |
| MOZ_ASSERT(isFree()); |
| } |
| }; |
| |
| #define FOR_EACH_PARSE_NODE_KIND(F) \ |
| F(NOP) \ |
| F(SEMI) \ |
| F(COMMA) \ |
| F(CONDITIONAL) \ |
| F(COLON) \ |
| F(SHORTHAND) \ |
| F(POS) \ |
| F(NEG) \ |
| F(PREINCREMENT) \ |
| F(POSTINCREMENT) \ |
| F(PREDECREMENT) \ |
| F(POSTDECREMENT) \ |
| F(DOT) \ |
| F(ELEM) \ |
| F(ARRAY) \ |
| F(ELISION) \ |
| F(STATEMENTLIST) \ |
| F(LABEL) \ |
| F(OBJECT) \ |
| F(CALL) \ |
| F(NAME) \ |
| F(OBJECT_PROPERTY_NAME) \ |
| F(COMPUTED_NAME) \ |
| F(NUMBER) \ |
| F(STRING) \ |
| F(TEMPLATE_STRING_LIST) \ |
| F(TEMPLATE_STRING) \ |
| F(TAGGED_TEMPLATE) \ |
| F(CALLSITEOBJ) \ |
| F(REGEXP) \ |
| F(TRUE) \ |
| F(FALSE) \ |
| F(NULL) \ |
| F(THIS) \ |
| F(FUNCTION) \ |
| F(MODULE) \ |
| F(IF) \ |
| F(SWITCH) \ |
| F(CASE) \ |
| F(WHILE) \ |
| F(DOWHILE) \ |
| F(FOR) \ |
| F(COMPREHENSIONFOR) \ |
| F(BREAK) \ |
| F(CONTINUE) \ |
| F(VAR) \ |
| F(CONST) \ |
| F(WITH) \ |
| F(RETURN) \ |
| F(NEW) \ |
| /* Delete operations. These must be sequential. */ \ |
| F(DELETENAME) \ |
| F(DELETEPROP) \ |
| F(DELETEELEM) \ |
| F(DELETEEXPR) \ |
| F(TRY) \ |
| F(CATCH) \ |
| F(CATCHLIST) \ |
| F(THROW) \ |
| F(DEBUGGER) \ |
| F(GENERATOR) \ |
| F(YIELD) \ |
| F(YIELD_STAR) \ |
| F(GENEXP) \ |
| F(ARRAYCOMP) \ |
| F(ARRAYPUSH) \ |
| F(LEXICALSCOPE) \ |
| F(LET) \ |
| F(LETBLOCK) \ |
| F(IMPORT) \ |
| F(IMPORT_SPEC_LIST) \ |
| F(IMPORT_SPEC) \ |
| F(EXPORT) \ |
| F(EXPORT_FROM) \ |
| F(EXPORT_DEFAULT) \ |
| F(EXPORT_SPEC_LIST) \ |
| F(EXPORT_SPEC) \ |
| F(EXPORT_BATCH_SPEC) \ |
| F(FORIN) \ |
| F(FOROF) \ |
| F(FORHEAD) \ |
| F(ARGSBODY) \ |
| F(SPREAD) \ |
| F(MUTATEPROTO) \ |
| F(CLASS) \ |
| F(CLASSMETHOD) \ |
| F(CLASSMETHODLIST) \ |
| F(CLASSNAMES) \ |
| F(NEWTARGET) \ |
| F(POSHOLDER) \ |
| F(SUPERBASE) \ |
| F(SUPERCALL) \ |
| F(SETTHIS) \ |
| \ |
| /* Unary operators. */ \ |
| F(TYPEOFNAME) \ |
| F(TYPEOFEXPR) \ |
| F(VOID) \ |
| F(NOT) \ |
| F(BITNOT) \ |
| \ |
| /* \ |
| * Binary operators. \ |
| * These must be in the same order as TOK_OR and friends in TokenStream.h. \ |
| */ \ |
| F(OR) \ |
| F(AND) \ |
| F(BITOR) \ |
| F(BITXOR) \ |
| F(BITAND) \ |
| F(STRICTEQ) \ |
| F(EQ) \ |
| F(STRICTNE) \ |
| F(NE) \ |
| F(LT) \ |
| F(LE) \ |
| F(GT) \ |
| F(GE) \ |
| F(INSTANCEOF) \ |
| F(IN) \ |
| F(LSH) \ |
| F(RSH) \ |
| F(URSH) \ |
| F(ADD) \ |
| F(SUB) \ |
| F(STAR) \ |
| F(DIV) \ |
| F(MOD) \ |
| F(POW) \ |
| \ |
| /* Assignment operators (= += -= etc.). */ \ |
| /* ParseNode::isAssignment assumes all these are consecutive. */ \ |
| F(ASSIGN) \ |
| F(ADDASSIGN) \ |
| F(SUBASSIGN) \ |
| F(BITORASSIGN) \ |
| F(BITXORASSIGN) \ |
| F(BITANDASSIGN) \ |
| F(LSHASSIGN) \ |
| F(RSHASSIGN) \ |
| F(URSHASSIGN) \ |
| F(MULASSIGN) \ |
| F(DIVASSIGN) \ |
| F(MODASSIGN) \ |
| F(POWASSIGN) |
| |
| /* |
| * Parsing builds a tree of nodes that directs code generation. This tree is |
| * not a concrete syntax tree in all respects (for example, || and && are left |
| * associative, but (A && B && C) translates into the right-associated tree |
| * <A && <B && C>> so that code generation can emit a left-associative branch |
| * around <B && C> when A is false). Nodes are labeled by kind, with a |
| * secondary JSOp label when needed. |
| * |
| * The long comment after this enum block describes the kinds in detail. |
| */ |
| enum ParseNodeKind |
| { |
| #define EMIT_ENUM(name) PNK_##name, |
| FOR_EACH_PARSE_NODE_KIND(EMIT_ENUM) |
| #undef EMIT_ENUM |
| PNK_LIMIT, /* domain size */ |
| PNK_BINOP_FIRST = PNK_OR, |
| PNK_BINOP_LAST = PNK_POW, |
| PNK_ASSIGNMENT_START = PNK_ASSIGN, |
| PNK_ASSIGNMENT_LAST = PNK_POWASSIGN |
| }; |
| |
| inline bool |
| IsDeleteKind(ParseNodeKind kind) |
| { |
| return PNK_DELETENAME <= kind && kind <= PNK_DELETEEXPR; |
| } |
| |
| /* |
| * Label Variant Members |
| * ----- ------- ------- |
| * <Definitions> |
| * PNK_FUNCTION name pn_funbox: ptr to js::FunctionBox holding function |
| * object containing arg and var properties. We |
| * create the function object at parse (not emit) |
| * time to specialize arg and var bytecodes early. |
| * pn_body: PNK_ARGSBODY, ordinarily; |
| * PNK_LEXICALSCOPE for implicit function in genexpr |
| * pn_scopecoord: hops and var index for function |
| * pn_dflags: PND_* definition/use flags (see below) |
| * pn_blockid: block id number |
| * PNK_ARGSBODY list list of formal parameters with |
| * PNK_NAME node with non-empty name for |
| * SingleNameBinding without Initializer |
| * PNK_ASSIGN node for SingleNameBinding with |
| * Initializer |
| * PNK_NAME node with empty name for destructuring |
| * pn_expr: PNK_ARRAY, PNK_OBJECT, or PNK_ASSIGN |
| * PNK_ARRAY or PNK_OBJECT for BindingPattern |
| * without Initializer |
| * PNK_ASSIGN for BindingPattern with |
| * Initializer |
| * followed by: |
| * PNK_STATEMENTLIST node for function body |
| * statements, |
| * PNK_RETURN for expression closure |
| * pn_count: 1 + number of formal parameters |
| * pn_tree: PNK_ARGSBODY or PNK_STATEMENTLIST node |
| * PNK_SPREAD unary pn_kid: expression being spread |
| * |
| * <Statements> |
| * PNK_STATEMENTLIST list pn_head: list of pn_count statements |
| * PNK_IF ternary pn_kid1: cond, pn_kid2: then, pn_kid3: else or null. |
| * In body of a comprehension or desugared generator |
| * expression, pn_kid2 is PNK_YIELD, PNK_ARRAYPUSH, |
| * or (if the push was optimized away) empty |
| * PNK_STATEMENTLIST. |
| * PNK_SWITCH binary pn_left: discriminant |
| * pn_right: list of PNK_CASE nodes, with at most one |
| * default node, or if there are let bindings |
| * in the top level of the switch body's cases, a |
| * PNK_LEXICALSCOPE node that contains the list of |
| * PNK_CASE nodes. |
| * PNK_CASE binary pn_left: case-expression if CaseClause, or |
| * null if DefaultClause |
| * pn_right: PNK_STATEMENTLIST node for this case's |
| * statements |
| * pn_u.binary.offset: scratch space for the emitter |
| * PNK_WHILE binary pn_left: cond, pn_right: body |
| * PNK_DOWHILE binary pn_left: body, pn_right: cond |
| * PNK_FOR binary pn_left: either PNK_FORIN (for-in statement), |
| * PNK_FOROF (for-of) or PNK_FORHEAD (for(;;)) |
| * pn_right: body |
| * PNK_COMPREHENSIONFOR pn_left: either PNK_FORIN or PNK_FOROF |
| * binary pn_right: body |
| * PNK_FORIN ternary pn_kid1: PNK_VAR to left of 'in', or nullptr |
| * pn_kid2: PNK_NAME or destructuring expr |
| * to left of 'in'; if pn_kid1, then this |
| * is a clone of pn_kid1->pn_head |
| * pn_kid3: object expr to right of 'in' |
| * PNK_FOROF ternary pn_kid1: PNK_VAR to left of 'of', or nullptr |
| * pn_kid2: PNK_NAME or destructuring expr |
| * to left of 'of'; if pn_kid1, then this |
| * is a clone of pn_kid1->pn_head |
| * pn_kid3: expr to right of 'of' |
| * PNK_FORHEAD ternary pn_kid1: init expr before first ';' or nullptr |
| * pn_kid2: cond expr before second ';' or nullptr |
| * pn_kid3: update expr after second ';' or nullptr |
| * PNK_THROW unary pn_op: JSOP_THROW, pn_kid: exception |
| * PNK_TRY ternary pn_kid1: try block |
| * pn_kid2: null or PNK_CATCHLIST list |
| * pn_kid3: null or finally block |
| * PNK_CATCHLIST list pn_head: list of PNK_LEXICALSCOPE nodes, one per |
| * catch-block, each with pn_expr pointing |
| * to a PNK_CATCH node |
| * PNK_CATCH ternary pn_kid1: PNK_NAME, PNK_ARRAY, or PNK_OBJECT catch var node |
| * (PNK_ARRAY or PNK_OBJECT if destructuring) |
| * pn_kid2: null or the catch guard expression |
| * pn_kid3: catch block statements |
| * PNK_BREAK name pn_atom: label or null |
| * PNK_CONTINUE name pn_atom: label or null |
| * PNK_WITH binary-obj pn_left: head expr; pn_right: body; pn_binary_obj: StaticWithObject |
| * PNK_VAR, list pn_head: list of PNK_NAME or PNK_ASSIGN nodes |
| * PNK_CONST each name node has either |
| * pn_used: false |
| * pn_atom: variable name |
| * pn_expr: initializer or null |
| * or |
| * pn_used: true |
| * pn_atom: variable name |
| * pn_lexdef: def node |
| * each assignment node has |
| * pn_left: PNK_NAME with pn_used true and |
| * pn_lexdef (NOT pn_expr) set |
| * pn_right: initializer |
| * PNK_RETURN unary pn_kid: return expr or null |
| * PNK_SEMI unary pn_kid: expr or null statement |
| * pn_prologue: true if Directive Prologue member |
| * in original source, not introduced via |
| * constant folding or other tree rewriting |
| * PNK_LABEL name pn_atom: label, pn_expr: labeled statement |
| * PNK_IMPORT binary pn_left: PNK_IMPORT_SPEC_LIST import specifiers |
| * pn_right: PNK_STRING module specifier |
| * PNK_EXPORT unary pn_kid: declaration expression |
| * PNK_EXPORT_FROM binary pn_left: PNK_EXPORT_SPEC_LIST export specifiers |
| * pn_right: PNK_STRING module specifier |
| * PNK_EXPORT_DEFAULT unary pn_kid: export default declaration or expression |
| * |
| * <Expressions> |
| * All left-associated binary trees of the same type are optimized into lists |
| * to avoid recursion when processing expression chains. |
| * PNK_COMMA list pn_head: list of pn_count comma-separated exprs |
| * PNK_ASSIGN binary pn_left: lvalue, pn_right: rvalue |
| * PNK_ADDASSIGN, binary pn_left: lvalue, pn_right: rvalue |
| * PNK_SUBASSIGN, pn_op: JSOP_ADD for +=, etc. |
| * PNK_BITORASSIGN, |
| * PNK_BITXORASSIGN, |
| * PNK_BITANDASSIGN, |
| * PNK_LSHASSIGN, |
| * PNK_RSHASSIGN, |
| * PNK_URSHASSIGN, |
| * PNK_MULASSIGN, |
| * PNK_DIVASSIGN, |
| * PNK_MODASSIGN, |
| * PNK_POWASSIGN |
| * PNK_CONDITIONAL ternary (cond ? trueExpr : falseExpr) |
| * pn_kid1: cond, pn_kid2: then, pn_kid3: else |
| * PNK_OR, list pn_head; list of pn_count subexpressions |
| * PNK_AND, All of these operators are left-associative except (**). |
| * PNK_BITOR, |
| * PNK_BITXOR, |
| * PNK_BITAND, |
| * PNK_EQ, |
| * PNK_NE, |
| * PNK_STRICTEQ, |
| * PNK_STRICTNE, |
| * PNK_LT, |
| * PNK_LE, |
| * PNK_GT, |
| * PNK_GE, |
| * PNK_LSH, |
| * PNK_RSH, |
| * PNK_URSH, |
| * PNK_ADD, |
| * PNK_SUB, |
| * PNK_STAR, |
| * PNK_DIV, |
| * PNK_MOD, |
| * PNK_POW (**) is right-associative, but forms a list |
| * nonetheless. Special hacks everywhere. |
| * |
| * PNK_POS, unary pn_kid: UNARY expr |
| * PNK_NEG |
| * PNK_VOID, unary pn_kid: UNARY expr |
| * PNK_NOT, |
| * PNK_BITNOT |
| * PNK_TYPEOFNAME, unary pn_kid: UNARY expr |
| * PNK_TYPEOFEXPR |
| * PNK_PREINCREMENT, unary pn_kid: MEMBER expr |
| * PNK_POSTINCREMENT, |
| * PNK_PREDECREMENT, |
| * PNK_POSTDECREMENT |
| * PNK_NEW list pn_head: list of ctor, arg1, arg2, ... argN |
| * pn_count: 1 + N (where N is number of args) |
| * ctor is a MEMBER expr |
| * PNK_DELETENAME unary pn_kid: PNK_NAME expr |
| * PNK_DELETEPROP unary pn_kid: PNK_DOT expr |
| * PNK_DELETEELEM unary pn_kid: PNK_ELEM expr |
| * PNK_DELETESUPERELEM unary pn_kid: PNK_SUPERELEM expr |
| * PNK_DELETEEXPR unary pn_kid: MEMBER expr that's evaluated, then the |
| * overall delete evaluates to true; can't be a kind |
| * for a more-specific PNK_DELETE* unless constant |
| * folding (or a similar parse tree manipulation) has |
| * occurred |
| * PNK_DOT name pn_expr: MEMBER expr to left of . |
| * pn_atom: name to right of . |
| * PNK_ELEM binary pn_left: MEMBER expr to left of [ |
| * pn_right: expr between [ and ] |
| * PNK_CALL list pn_head: list of call, arg1, arg2, ... argN |
| * pn_count: 1 + N (where N is number of args) |
| * call is a MEMBER expr naming a callable object |
| * PNK_GENEXP list Exactly like PNK_CALL, used for the implicit call |
| * in the desugaring of a generator-expression. |
| * PNK_ARRAY list pn_head: list of pn_count array element exprs |
| * [,,] holes are represented by PNK_ELISION nodes |
| * pn_xflags: PN_ENDCOMMA if extra comma at end |
| * PNK_OBJECT list pn_head: list of pn_count binary PNK_COLON nodes |
| * PNK_COLON binary key-value pair in object initializer or |
| * destructuring lhs |
| * pn_left: property id, pn_right: value |
| * PNK_SHORTHAND binary Same fields as PNK_COLON. This is used for object |
| * literal properties using shorthand ({x}). |
| * PNK_COMPUTED_NAME unary ES6 ComputedPropertyName. |
| * pn_kid: the AssignmentExpression inside the square brackets |
| * PNK_NAME, name pn_atom: name, string, or object atom |
| * PNK_STRING pn_op: JSOP_GETNAME, JSOP_STRING, or JSOP_OBJECT |
| * If JSOP_GETNAME, pn_op may be JSOP_*ARG or JSOP_*VAR |
| * with pn_scoppecord telling (hops, slot) and pn_dflags |
| * telling const-ness and static analysis results |
| * PNK_TEMPLATE_STRING_LIST pn_head: list of alternating expr and template strings |
| * list |
| * PNK_TEMPLATE_STRING pn_atom: template string atom |
| nullary pn_op: JSOP_NOP |
| * PNK_TAGGED_TEMPLATE pn_head: list of call, call site object, arg1, arg2, ... argN |
| * list pn_count: 2 + N (N is the number of substitutions) |
| * PNK_CALLSITEOBJ list pn_head: a PNK_ARRAY node followed by |
| * list of pn_count - 1 PNK_TEMPLATE_STRING nodes |
| * PNK_REGEXP nullary pn_objbox: RegExp model object |
| * PNK_NAME name If pn_used, PNK_NAME uses the lexdef member instead |
| * of the expr member it overlays |
| * PNK_NUMBER dval pn_dval: double value of numeric literal |
| * PNK_TRUE, nullary pn_op: JSOp bytecode |
| * PNK_FALSE, |
| * PNK_NULL |
| * |
| * PNK_THIS, unary pn_kid: '.this' Name if function `this`, else nullptr |
| * PNK_SUPERBASE unary pn_kid: '.this' Name |
| * |
| * PNK_SETTHIS binary pn_left: '.this' Name, pn_right: SuperCall |
| * |
| * PNK_LEXICALSCOPE name pn_objbox: block object in ObjectBox holder |
| * pn_expr: block body |
| * PNK_GENERATOR nullary |
| * PNK_YIELD, binary pn_left: expr or null; pn_right: generator object |
| * PNK_YIELD_STAR |
| * PNK_ARRAYCOMP list pn_count: 1 |
| * pn_head: list of 1 element, which is block |
| * enclosing for loop(s) and optionally |
| * if-guarded PNK_ARRAYPUSH |
| * PNK_ARRAYPUSH unary pn_op: JSOP_ARRAYCOMP |
| * pn_kid: array comprehension expression |
| * PNK_NOP nullary |
| */ |
| enum ParseNodeArity |
| { |
| PN_NULLARY, /* 0 kids, only pn_atom/pn_dval/etc. */ |
| PN_UNARY, /* one kid, plus a couple of scalars */ |
| PN_BINARY, /* two kids, plus a couple of scalars */ |
| PN_BINARY_OBJ, /* two kids, plus an objbox */ |
| PN_TERNARY, /* three kids */ |
| PN_CODE, /* module or function definition node */ |
| PN_LIST, /* generic singly linked list */ |
| PN_NAME /* name use or definition node */ |
| }; |
| |
| struct Definition; |
| |
| class LoopControlStatement; |
| class BreakStatement; |
| class ContinueStatement; |
| class ConditionalExpression; |
| class PropertyAccess; |
| |
| class ParseNode |
| { |
| uint32_t pn_type : 16, /* PNK_* type */ |
| pn_op : 8, /* see JSOp enum and jsopcode.tbl */ |
| pn_arity : 4, /* see ParseNodeArity enum */ |
| pn_parens : 1, /* this expr was enclosed in parens */ |
| pn_used : 1, /* name node is on a use-chain */ |
| pn_defn : 1; /* this node is a Definition */ |
| |
| ParseNode(const ParseNode& other) = delete; |
| void operator=(const ParseNode& other) = delete; |
| |
| public: |
| ParseNode(ParseNodeKind kind, JSOp op, ParseNodeArity arity) |
| : pn_type(kind), pn_op(op), pn_arity(arity), pn_parens(0), pn_used(0), pn_defn(0), |
| pn_pos(0, 0), pn_next(nullptr), pn_link(nullptr) |
| { |
| MOZ_ASSERT(kind < PNK_LIMIT); |
| memset(&pn_u, 0, sizeof pn_u); |
| } |
| |
| ParseNode(ParseNodeKind kind, JSOp op, ParseNodeArity arity, const TokenPos& pos) |
| : pn_type(kind), pn_op(op), pn_arity(arity), pn_parens(0), pn_used(0), pn_defn(0), |
| pn_pos(pos), pn_next(nullptr), pn_link(nullptr) |
| { |
| MOZ_ASSERT(kind < PNK_LIMIT); |
| memset(&pn_u, 0, sizeof pn_u); |
| } |
| |
| JSOp getOp() const { return JSOp(pn_op); } |
| void setOp(JSOp op) { pn_op = op; } |
| bool isOp(JSOp op) const { return getOp() == op; } |
| |
| ParseNodeKind getKind() const { |
| MOZ_ASSERT(pn_type < PNK_LIMIT); |
| return ParseNodeKind(pn_type); |
| } |
| void setKind(ParseNodeKind kind) { |
| MOZ_ASSERT(kind < PNK_LIMIT); |
| pn_type = kind; |
| } |
| bool isKind(ParseNodeKind kind) const { return getKind() == kind; } |
| |
| ParseNodeArity getArity() const { return ParseNodeArity(pn_arity); } |
| bool isArity(ParseNodeArity a) const { return getArity() == a; } |
| void setArity(ParseNodeArity a) { pn_arity = a; } |
| |
| bool isAssignment() const { |
| ParseNodeKind kind = getKind(); |
| return PNK_ASSIGNMENT_START <= kind && kind <= PNK_ASSIGNMENT_LAST; |
| } |
| |
| bool isBinaryOperation() const { |
| ParseNodeKind kind = getKind(); |
| return PNK_BINOP_FIRST <= kind && kind <= PNK_BINOP_LAST; |
| } |
| |
| /* Boolean attributes. */ |
| bool isInParens() const { return pn_parens; } |
| bool isLikelyIIFE() const { return isInParens(); } |
| void setInParens(bool enabled) { pn_parens = enabled; } |
| bool isUsed() const { return pn_used; } |
| void setUsed(bool enabled) { pn_used = enabled; } |
| bool isDefn() const { return pn_defn; } |
| void setDefn(bool enabled) { pn_defn = enabled; } |
| |
| static const unsigned NumDefinitionFlagBits = 10; |
| static const unsigned NumListFlagBits = 10; |
| static const unsigned NumBlockIdBits = 22; |
| static_assert(NumDefinitionFlagBits == NumListFlagBits, |
| "Assumed below to achieve consistent blockid offset"); |
| static_assert(NumDefinitionFlagBits + NumBlockIdBits <= 32, |
| "This is supposed to fit in a single uint32_t"); |
| |
| TokenPos pn_pos; /* two 16-bit pairs here, for 64 bits */ |
| ParseNode* pn_next; /* intrinsic link in parent PN_LIST */ |
| |
| /* |
| * Nodes that represent lexical bindings may, in addition to being |
| * ParseNodes, also be Definition nodes. (Definition is defined far below, |
| * with a lengthy comment that you should read.) Each binding has one |
| * canonical Definition; all uses of that definition are reached starting |
| * from dn_uses, then following subsequent pn_link pointers. |
| * |
| * The dn_uses chain elements are unordered. Any apparent ordering in some |
| * cases, will not be present in all others. |
| */ |
| union { |
| ParseNode* dn_uses; |
| ParseNode* pn_link; |
| }; |
| |
| union { |
| struct { /* list of next-linked nodes */ |
| ParseNode* head; /* first node in list */ |
| ParseNode** tail; /* ptr to ptr to last node in list */ |
| uint32_t count; /* number of nodes in list */ |
| uint32_t xflags:NumListFlagBits, /* see PNX_* below */ |
| blockid:NumBlockIdBits; /* see name variant below */ |
| } list; |
| struct { /* ternary: if, for(;;), ?: */ |
| ParseNode* kid1; /* condition, discriminant, etc. */ |
| ParseNode* kid2; /* then-part, case list, etc. */ |
| ParseNode* kid3; /* else-part, default case, etc. */ |
| } ternary; |
| struct { /* two kids if binary */ |
| ParseNode* left; |
| ParseNode* right; |
| union { |
| unsigned iflags; /* JSITER_* flags for PNK_{COMPREHENSION,}FOR node */ |
| ObjectBox* objbox; /* only for PN_BINARY_OBJ */ |
| bool isStatic; /* only for PNK_CLASSMETHOD */ |
| uint32_t offset; /* for the emitter's use on PNK_CASE nodes */ |
| }; |
| } binary; |
| struct { /* one kid if unary */ |
| ParseNode* kid; |
| bool prologue; /* directive prologue member (as |
| pn_prologue) */ |
| } unary; |
| struct { /* name, labeled statement, etc. */ |
| union { |
| JSAtom* atom; /* lexical name or label atom */ |
| ObjectBox* objbox; /* block or regexp object */ |
| FunctionBox* funbox; /* function object */ |
| ModuleBox* modulebox; /* module object */ |
| }; |
| union { |
| ParseNode* expr; /* module or function body, var |
| initializer, argument default, or |
| base object of PNK_DOT */ |
| Definition* lexdef; /* lexical definition for this use */ |
| }; |
| PackedScopeCoordinate scopeCoord; |
| uint32_t dflags:NumDefinitionFlagBits, /* see PND_* below */ |
| blockid:NumBlockIdBits; /* block number, for subset dominance |
| computation */ |
| } name; |
| struct { |
| double value; /* aligned numeric literal value */ |
| DecimalPoint decimalPoint; /* Whether the number has a decimal point */ |
| } number; |
| class { |
| friend class LoopControlStatement; |
| PropertyName* label; /* target of break/continue statement */ |
| } loopControl; |
| } pn_u; |
| |
| #define pn_modulebox pn_u.name.modulebox |
| #define pn_objbox pn_u.name.objbox |
| #define pn_funbox pn_u.name.funbox |
| #define pn_body pn_u.name.expr |
| #define pn_scopecoord pn_u.name.scopeCoord |
| #define pn_dflags pn_u.name.dflags |
| #define pn_blockid pn_u.name.blockid |
| #define pn_head pn_u.list.head |
| #define pn_tail pn_u.list.tail |
| #define pn_count pn_u.list.count |
| #define pn_xflags pn_u.list.xflags |
| #define pn_kid1 pn_u.ternary.kid1 |
| #define pn_kid2 pn_u.ternary.kid2 |
| #define pn_kid3 pn_u.ternary.kid3 |
| #define pn_left pn_u.binary.left |
| #define pn_right pn_u.binary.right |
| #define pn_pval pn_u.binary.pval |
| #define pn_iflags pn_u.binary.iflags |
| #define pn_binary_obj pn_u.binary.objbox |
| #define pn_kid pn_u.unary.kid |
| #define pn_prologue pn_u.unary.prologue |
| #define pn_atom pn_u.name.atom |
| #define pn_objbox pn_u.name.objbox |
| #define pn_expr pn_u.name.expr |
| #define pn_lexdef pn_u.name.lexdef |
| #define pn_dval pn_u.number.value |
| |
| protected: |
| void init(TokenKind type, JSOp op, ParseNodeArity arity) { |
| pn_type = type; |
| pn_op = op; |
| pn_arity = arity; |
| pn_parens = false; |
| MOZ_ASSERT(!pn_used); |
| MOZ_ASSERT(!pn_defn); |
| pn_next = pn_link = nullptr; |
| } |
| |
| public: |
| /* |
| * If |left| is a list of the given kind/left-associative op, append |
| * |right| to it and return |left|. Otherwise return a [left, right] list. |
| */ |
| static ParseNode* |
| appendOrCreateList(ParseNodeKind kind, JSOp op, ParseNode* left, ParseNode* right, |
| FullParseHandler* handler, ParseContext<FullParseHandler>* pc); |
| |
| inline PropertyName* name() const; |
| inline JSAtom* atom() const; |
| |
| /* |
| * The pn_expr and lexdef members are arms of an unsafe union. Unless you |
| * know exactly what you're doing, use only the following methods to access |
| * them. For less overhead and assertions for protection, use pn->expr() |
| * and pn->lexdef(). Otherwise, use pn->maybeExpr() and pn->maybeLexDef(). |
| */ |
| ParseNode* expr() const { |
| MOZ_ASSERT(!pn_used); |
| MOZ_ASSERT(pn_arity == PN_NAME || pn_arity == PN_CODE); |
| return pn_expr; |
| } |
| |
| Definition* lexdef() const { |
| MOZ_ASSERT(pn_used || isDeoptimized()); |
| MOZ_ASSERT(pn_arity == PN_NAME); |
| return pn_lexdef; |
| } |
| |
| ParseNode* maybeExpr() { return pn_used ? nullptr : expr(); } |
| Definition* maybeLexDef() { return pn_used ? lexdef() : nullptr; } |
| |
| Definition* resolve(); |
| |
| /* PN_CODE and PN_NAME pn_dflags bits. */ |
| #define PND_LEXICAL 0x01 /* lexical (block-scoped) binding or use of a hoisted |
| let or const */ |
| #define PND_CONST 0x02 /* const binding (orthogonal to let) */ |
| #define PND_ASSIGNED 0x04 /* set if ever LHS of assignment */ |
| #define PND_PLACEHOLDER 0x08 /* placeholder definition for lexdep */ |
| #define PND_BOUND 0x10 /* bound to a stack or global slot */ |
| #define PND_DEOPTIMIZED 0x20 /* former pn_used name node, pn_lexdef |
| still valid, but this use no longer |
| optimizable via an upvar opcode */ |
| #define PND_CLOSED 0x40 /* variable is closed over */ |
| #define PND_KNOWNALIASED 0x80 /* definition known to be aliased and |
| already has a translated pnk_scopecoord */ |
| #define PND_IMPLICITARGUMENTS 0x100 /* the definition is a placeholder for |
| 'arguments' that has been converted |
| into a definition after the function |
| body has been parsed. */ |
| #define PND_IMPORT 0x200 /* the definition is a module import. */ |
| |
| static_assert(PND_IMPORT < (1 << NumDefinitionFlagBits), "Not enough bits"); |
| |
| /* Flags to propagate from uses to definition. */ |
| #define PND_USE2DEF_FLAGS (PND_ASSIGNED | PND_CLOSED) |
| |
| /* PN_LIST pn_xflags bits. */ |
| #define PNX_FUNCDEFS 0x01 /* contains top-level function statements */ |
| #define PNX_SETCALL 0x02 /* call expression in lvalue context */ |
| #define PNX_ARRAYHOLESPREAD 0x04 /* one or more of |
| 1. array initialiser has holes |
| 2. array initializer has spread node */ |
| #define PNX_NONCONST 0x08 /* initialiser has non-constants */ |
| |
| static_assert(PNX_NONCONST < (1 << NumListFlagBits), "Not enough bits"); |
| |
| uint32_t frameSlot() const { |
| MOZ_ASSERT(pn_arity == PN_CODE || pn_arity == PN_NAME); |
| return pn_scopecoord.slot(); |
| } |
| |
| bool functionIsHoisted() const { |
| MOZ_ASSERT(pn_arity == PN_CODE && getKind() == PNK_FUNCTION); |
| MOZ_ASSERT(isOp(JSOP_LAMBDA) || // lambda, genexpr |
| isOp(JSOP_LAMBDA_ARROW) || // arrow function |
| isOp(JSOP_DEFFUN) || // non-body-level function statement |
| isOp(JSOP_NOP) || // body-level function stmt in global code |
| isOp(JSOP_GETLOCAL) || // body-level function stmt in function code |
| isOp(JSOP_GETARG)); // body-level function redeclaring formal |
| return !isOp(JSOP_LAMBDA) && !isOp(JSOP_LAMBDA_ARROW) && !isOp(JSOP_DEFFUN); |
| } |
| |
| /* |
| * True if this statement node could be a member of a Directive Prologue: an |
| * expression statement consisting of a single string literal. |
| * |
| * This considers only the node and its children, not its context. After |
| * parsing, check the node's pn_prologue flag to see if it is indeed part of |
| * a directive prologue. |
| * |
| * Note that a Directive Prologue can contain statements that cannot |
| * themselves be directives (string literals that include escape sequences |
| * or escaped newlines, say). This member function returns true for such |
| * nodes; we use it to determine the extent of the prologue. |
| */ |
| JSAtom* isStringExprStatement() const { |
| if (getKind() == PNK_SEMI) { |
| MOZ_ASSERT(pn_arity == PN_UNARY); |
| ParseNode* kid = pn_kid; |
| if (kid && kid->getKind() == PNK_STRING && !kid->pn_parens) |
| return kid->pn_atom; |
| } |
| return nullptr; |
| } |
| |
| inline bool test(unsigned flag) const; |
| |
| bool isLexical() const { return test(PND_LEXICAL) && !isUsed(); } |
| bool isConst() const { return test(PND_CONST); } |
| bool isPlaceholder() const { return test(PND_PLACEHOLDER); } |
| bool isDeoptimized() const { return test(PND_DEOPTIMIZED); } |
| bool isAssigned() const { return test(PND_ASSIGNED); } |
| bool isClosed() const { return test(PND_CLOSED); } |
| bool isBound() const { return test(PND_BOUND); } |
| bool isImplicitArguments() const { return test(PND_IMPLICITARGUMENTS); } |
| bool isHoistedLexicalUse() const { return test(PND_LEXICAL) && isUsed(); } |
| bool isKnownAliased() const { return test(PND_KNOWNALIASED); } |
| bool isImport() const { return test(PND_IMPORT); } |
| |
| /* True if pn is a parsenode representing a literal constant. */ |
| bool isLiteral() const { |
| return isKind(PNK_NUMBER) || |
| isKind(PNK_STRING) || |
| isKind(PNK_TRUE) || |
| isKind(PNK_FALSE) || |
| isKind(PNK_NULL); |
| } |
| |
| /* Return true if this node appears in a Directive Prologue. */ |
| bool isDirectivePrologueMember() const { return pn_prologue; } |
| |
| #ifdef JS_HAS_GENERATOR_EXPRS |
| ParseNode* generatorExpr() const { |
| MOZ_ASSERT(isKind(PNK_GENEXP)); |
| ParseNode* callee = this->pn_head; |
| ParseNode* body = callee->pn_body; |
| MOZ_ASSERT(body->isKind(PNK_STATEMENTLIST)); |
| MOZ_ASSERT(body->last()->isKind(PNK_LEXICALSCOPE) || |
| body->last()->isKind(PNK_COMPREHENSIONFOR)); |
| return body->last(); |
| } |
| #endif |
| |
| inline void markAsAssigned(); |
| |
| /* |
| * Compute a pointer to the last element in a singly-linked list. NB: list |
| * must be non-empty for correct PN_LAST usage -- this is asserted! |
| */ |
| ParseNode* last() const { |
| MOZ_ASSERT(pn_arity == PN_LIST); |
| MOZ_ASSERT(pn_count != 0); |
| return (ParseNode*)(uintptr_t(pn_tail) - offsetof(ParseNode, pn_next)); |
| } |
| |
| void initNumber(double value, DecimalPoint decimalPoint) { |
| MOZ_ASSERT(pn_arity == PN_NULLARY); |
| MOZ_ASSERT(getKind() == PNK_NUMBER); |
| pn_u.number.value = value; |
| pn_u.number.decimalPoint = decimalPoint; |
| } |
| |
| void makeEmpty() { |
| MOZ_ASSERT(pn_arity == PN_LIST); |
| pn_head = nullptr; |
| pn_tail = &pn_head; |
| pn_count = 0; |
| pn_xflags = 0; |
| pn_blockid = 0; |
| } |
| |
| void initList(ParseNode* pn) { |
| MOZ_ASSERT(pn_arity == PN_LIST); |
| if (pn->pn_pos.begin < pn_pos.begin) |
| pn_pos.begin = pn->pn_pos.begin; |
| pn_pos.end = pn->pn_pos.end; |
| pn_head = pn; |
| pn_tail = &pn->pn_next; |
| pn_count = 1; |
| pn_xflags = 0; |
| pn_blockid = 0; |
| } |
| |
| void append(ParseNode* pn) { |
| MOZ_ASSERT(pn_arity == PN_LIST); |
| MOZ_ASSERT(pn->pn_pos.begin >= pn_pos.begin); |
| pn_pos.end = pn->pn_pos.end; |
| *pn_tail = pn; |
| pn_tail = &pn->pn_next; |
| pn_count++; |
| } |
| |
| void prepend(ParseNode* pn) { |
| MOZ_ASSERT(pn_arity == PN_LIST); |
| pn->pn_next = pn_head; |
| pn_head = pn; |
| if (pn_tail == &pn_head) |
| pn_tail = &pn->pn_next; |
| pn_count++; |
| } |
| |
| void checkListConsistency() |
| #ifndef DEBUG |
| {} |
| #endif |
| ; |
| |
| enum AllowConstantObjects { |
| DontAllowObjects = 0, |
| AllowObjects, |
| ForCopyOnWriteArray |
| }; |
| |
| bool getConstantValue(ExclusiveContext* cx, AllowConstantObjects allowObjects, MutableHandleValue vp, |
| Value* compare = nullptr, size_t ncompare = 0, |
| NewObjectKind newKind = TenuredObject); |
| inline bool isConstant(); |
| |
| template <class NodeType> |
| inline bool is() const { |
| return NodeType::test(*this); |
| } |
| |
| /* Casting operations. */ |
| template <class NodeType> |
| inline NodeType& as() { |
| MOZ_ASSERT(NodeType::test(*this)); |
| return *static_cast<NodeType*>(this); |
| } |
| |
| template <class NodeType> |
| inline const NodeType& as() const { |
| MOZ_ASSERT(NodeType::test(*this)); |
| return *static_cast<const NodeType*>(this); |
| } |
| |
| #ifdef DEBUG |
| void dump(); |
| void dump(int indent); |
| #endif |
| }; |
| |
| struct NullaryNode : public ParseNode |
| { |
| NullaryNode(ParseNodeKind kind, const TokenPos& pos) |
| : ParseNode(kind, JSOP_NOP, PN_NULLARY, pos) {} |
| NullaryNode(ParseNodeKind kind, JSOp op, const TokenPos& pos) |
| : ParseNode(kind, op, PN_NULLARY, pos) {} |
| |
| // This constructor is for a few mad uses in the emitter. It populates |
| // the pn_atom field even though that field belongs to a branch in pn_u |
| // that nullary nodes shouldn't use -- bogus. |
| NullaryNode(ParseNodeKind kind, JSOp op, const TokenPos& pos, JSAtom* atom) |
| : ParseNode(kind, op, PN_NULLARY, pos) |
| { |
| pn_atom = atom; |
| } |
| |
| #ifdef DEBUG |
| void dump(); |
| #endif |
| }; |
| |
| struct UnaryNode : public ParseNode |
| { |
| UnaryNode(ParseNodeKind kind, JSOp op, const TokenPos& pos, ParseNode* kid) |
| : ParseNode(kind, op, PN_UNARY, pos) |
| { |
| pn_kid = kid; |
| } |
| |
| #ifdef DEBUG |
| void dump(int indent); |
| #endif |
| }; |
| |
| struct BinaryNode : public ParseNode |
| { |
| BinaryNode(ParseNodeKind kind, JSOp op, const TokenPos& pos, ParseNode* left, ParseNode* right) |
| : ParseNode(kind, op, PN_BINARY, pos) |
| { |
| pn_left = left; |
| pn_right = right; |
| } |
| |
| BinaryNode(ParseNodeKind kind, JSOp op, ParseNode* left, ParseNode* right) |
| : ParseNode(kind, op, PN_BINARY, TokenPos::box(left->pn_pos, right->pn_pos)) |
| { |
| pn_left = left; |
| pn_right = right; |
| } |
| |
| #ifdef DEBUG |
| void dump(int indent); |
| #endif |
| }; |
| |
| struct BinaryObjNode : public ParseNode |
| { |
| BinaryObjNode(ParseNodeKind kind, JSOp op, const TokenPos& pos, ParseNode* left, ParseNode* right, |
| ObjectBox* objbox) |
| : ParseNode(kind, op, PN_BINARY_OBJ, pos) |
| { |
| pn_left = left; |
| pn_right = right; |
| pn_binary_obj = objbox; |
| } |
| |
| #ifdef DEBUG |
| void dump(int indent); |
| #endif |
| }; |
| |
| struct TernaryNode : public ParseNode |
| { |
| TernaryNode(ParseNodeKind kind, JSOp op, ParseNode* kid1, ParseNode* kid2, ParseNode* kid3) |
| : ParseNode(kind, op, PN_TERNARY, |
| TokenPos((kid1 ? kid1 : kid2 ? kid2 : kid3)->pn_pos.begin, |
| (kid3 ? kid3 : kid2 ? kid2 : kid1)->pn_pos.end)) |
| { |
| pn_kid1 = kid1; |
| pn_kid2 = kid2; |
| pn_kid3 = kid3; |
| } |
| |
| TernaryNode(ParseNodeKind kind, JSOp op, ParseNode* kid1, ParseNode* kid2, ParseNode* kid3, |
| const TokenPos& pos) |
| : ParseNode(kind, op, PN_TERNARY, pos) |
| { |
| pn_kid1 = kid1; |
| pn_kid2 = kid2; |
| pn_kid3 = kid3; |
| } |
| |
| #ifdef DEBUG |
| void dump(int indent); |
| #endif |
| }; |
| |
| struct ListNode : public ParseNode |
| { |
| ListNode(ParseNodeKind kind, const TokenPos& pos) |
| : ParseNode(kind, JSOP_NOP, PN_LIST, pos) |
| { |
| makeEmpty(); |
| } |
| |
| ListNode(ParseNodeKind kind, JSOp op, const TokenPos& pos) |
| : ParseNode(kind, op, PN_LIST, pos) |
| { |
| makeEmpty(); |
| } |
| |
| ListNode(ParseNodeKind kind, JSOp op, ParseNode* kid) |
| : ParseNode(kind, op, PN_LIST, kid->pn_pos) |
| { |
| initList(kid); |
| } |
| |
| static bool test(const ParseNode& node) { |
| return node.isArity(PN_LIST); |
| } |
| |
| #ifdef DEBUG |
| void dump(int indent); |
| #endif |
| }; |
| |
| struct CodeNode : public ParseNode |
| { |
| CodeNode(ParseNodeKind kind, const TokenPos& pos) |
| : ParseNode(kind, JSOP_NOP, PN_CODE, pos) |
| { |
| MOZ_ASSERT(kind == PNK_FUNCTION || kind == PNK_MODULE); |
| MOZ_ASSERT(!pn_body); |
| MOZ_ASSERT(!pn_objbox); |
| MOZ_ASSERT(pn_dflags == 0); |
| pn_scopecoord.makeFree(); |
| } |
| |
| public: |
| #ifdef DEBUG |
| void dump(int indent); |
| #endif |
| }; |
| |
| struct NameNode : public ParseNode |
| { |
| NameNode(ParseNodeKind kind, JSOp op, JSAtom* atom, uint32_t blockid, |
| const TokenPos& pos) |
| : ParseNode(kind, op, PN_NAME, pos) |
| { |
| pn_atom = atom; |
| pn_expr = nullptr; |
| pn_scopecoord.makeFree(); |
| pn_dflags = 0; |
| pn_blockid = blockid; |
| MOZ_ASSERT(pn_blockid == blockid); // check for bitfield overflow |
| } |
| |
| #ifdef DEBUG |
| void dump(int indent); |
| #endif |
| }; |
| |
| struct LexicalScopeNode : public ParseNode |
| { |
| LexicalScopeNode(ObjectBox* blockBox, const TokenPos& pos) |
| : ParseNode(PNK_LEXICALSCOPE, JSOP_NOP, PN_NAME, pos) |
| { |
| MOZ_ASSERT(!pn_expr); |
| MOZ_ASSERT(pn_dflags == 0); |
| MOZ_ASSERT(pn_blockid == 0); |
| pn_objbox = blockBox; |
| pn_scopecoord.makeFree(); |
| } |
| |
| LexicalScopeNode(ObjectBox* blockBox, ParseNode* blockNode) |
| : ParseNode(PNK_LEXICALSCOPE, JSOP_NOP, PN_NAME, blockNode->pn_pos) |
| { |
| pn_objbox = blockBox; |
| pn_expr = blockNode; |
| pn_blockid = blockNode->pn_blockid; |
| } |
| |
| static bool test(const ParseNode& node) { |
| return node.isKind(PNK_LEXICALSCOPE); |
| } |
| }; |
| |
| class LabeledStatement : public ParseNode |
| { |
| public: |
| LabeledStatement(PropertyName* label, ParseNode* stmt, uint32_t begin) |
| : ParseNode(PNK_LABEL, JSOP_NOP, PN_NAME, TokenPos(begin, stmt->pn_pos.end)) |
| { |
| pn_atom = label; |
| pn_expr = stmt; |
| } |
| |
| PropertyName* label() const { |
| return pn_atom->asPropertyName(); |
| } |
| |
| ParseNode* statement() const { |
| return pn_expr; |
| } |
| |
| static bool test(const ParseNode& node) { |
| bool match = node.isKind(PNK_LABEL); |
| MOZ_ASSERT_IF(match, node.isArity(PN_NAME)); |
| MOZ_ASSERT_IF(match, node.isOp(JSOP_NOP)); |
| return match; |
| } |
| }; |
| |
| // Inside a switch statement, a CaseClause is a case-label and the subsequent |
| // statements. The same node type is used for DefaultClauses. The only |
| // difference is that their caseExpression() is null. |
| class CaseClause : public BinaryNode |
| { |
| public: |
| CaseClause(ParseNode* expr, ParseNode* stmts, uint32_t begin) |
| : BinaryNode(PNK_CASE, JSOP_NOP, TokenPos(begin, stmts->pn_pos.end), expr, stmts) {} |
| |
| ParseNode* caseExpression() const { return pn_left; } |
| bool isDefault() const { return !caseExpression(); } |
| ParseNode* statementList() const { return pn_right; } |
| |
| // The next CaseClause in the same switch statement. |
| CaseClause* next() const { return pn_next ? &pn_next->as<CaseClause>() : nullptr; } |
| |
| // Scratch space used by the emitter. |
| uint32_t offset() const { return pn_u.binary.offset; } |
| void setOffset(uint32_t u) { pn_u.binary.offset = u; } |
| |
| static bool test(const ParseNode& node) { |
| bool match = node.isKind(PNK_CASE); |
| MOZ_ASSERT_IF(match, node.isArity(PN_BINARY)); |
| MOZ_ASSERT_IF(match, node.isOp(JSOP_NOP)); |
| return match; |
| } |
| }; |
| |
| class LoopControlStatement : public ParseNode |
| { |
| protected: |
| LoopControlStatement(ParseNodeKind kind, PropertyName* label, const TokenPos& pos) |
| : ParseNode(kind, JSOP_NOP, PN_NULLARY, pos) |
| { |
| MOZ_ASSERT(kind == PNK_BREAK || kind == PNK_CONTINUE); |
| pn_u.loopControl.label = label; |
| } |
| |
| public: |
| /* Label associated with this break/continue statement, if any. */ |
| PropertyName* label() const { |
| return pn_u.loopControl.label; |
| } |
| |
| static bool test(const ParseNode& node) { |
| bool match = node.isKind(PNK_BREAK) || node.isKind(PNK_CONTINUE); |
| MOZ_ASSERT_IF(match, node.isArity(PN_NULLARY)); |
| MOZ_ASSERT_IF(match, node.isOp(JSOP_NOP)); |
| return match; |
| } |
| }; |
| |
| class BreakStatement : public LoopControlStatement |
| { |
| public: |
| BreakStatement(PropertyName* label, const TokenPos& pos) |
| : LoopControlStatement(PNK_BREAK, label, pos) |
| { } |
| |
| static bool test(const ParseNode& node) { |
| bool match = node.isKind(PNK_BREAK); |
| MOZ_ASSERT_IF(match, node.isArity(PN_NULLARY)); |
| MOZ_ASSERT_IF(match, node.isOp(JSOP_NOP)); |
| return match; |
| } |
| }; |
| |
| class ContinueStatement : public LoopControlStatement |
| { |
| public: |
| ContinueStatement(PropertyName* label, const TokenPos& pos) |
| : LoopControlStatement(PNK_CONTINUE, label, pos) |
| { } |
| |
| static bool test(const ParseNode& node) { |
| bool match = node.isKind(PNK_CONTINUE); |
| MOZ_ASSERT_IF(match, node.isArity(PN_NULLARY)); |
| MOZ_ASSERT_IF(match, node.isOp(JSOP_NOP)); |
| return match; |
| } |
| }; |
| |
| class DebuggerStatement : public ParseNode |
| { |
| public: |
| explicit DebuggerStatement(const TokenPos& pos) |
| : ParseNode(PNK_DEBUGGER, JSOP_NOP, PN_NULLARY, pos) |
| { } |
| }; |
| |
| class ConditionalExpression : public ParseNode |
| { |
| public: |
| ConditionalExpression(ParseNode* condition, ParseNode* thenExpr, ParseNode* elseExpr) |
| : ParseNode(PNK_CONDITIONAL, JSOP_NOP, PN_TERNARY, |
| TokenPos(condition->pn_pos.begin, elseExpr->pn_pos.end)) |
| { |
| MOZ_ASSERT(condition); |
| MOZ_ASSERT(thenExpr); |
| MOZ_ASSERT(elseExpr); |
| pn_u.ternary.kid1 = condition; |
| pn_u.ternary.kid2 = thenExpr; |
| pn_u.ternary.kid3 = elseExpr; |
| } |
| |
| ParseNode& condition() const { |
| return *pn_u.ternary.kid1; |
| } |
| |
| ParseNode& thenExpression() const { |
| return *pn_u.ternary.kid2; |
| } |
| |
| ParseNode& elseExpression() const { |
| return *pn_u.ternary.kid3; |
| } |
| |
| static bool test(const ParseNode& node) { |
| bool match = node.isKind(PNK_CONDITIONAL); |
| MOZ_ASSERT_IF(match, node.isArity(PN_TERNARY)); |
| MOZ_ASSERT_IF(match, node.isOp(JSOP_NOP)); |
| return match; |
| } |
| }; |
| |
| class ThisLiteral : public UnaryNode |
| { |
| public: |
| ThisLiteral(const TokenPos& pos, ParseNode* thisName) |
| : UnaryNode(PNK_THIS, JSOP_NOP, pos, thisName) |
| { } |
| }; |
| |
| class NullLiteral : public ParseNode |
| { |
| public: |
| explicit NullLiteral(const TokenPos& pos) : ParseNode(PNK_NULL, JSOP_NULL, PN_NULLARY, pos) { } |
| }; |
| |
| class BooleanLiteral : public ParseNode |
| { |
| public: |
| BooleanLiteral(bool b, const TokenPos& pos) |
| : ParseNode(b ? PNK_TRUE : PNK_FALSE, b ? JSOP_TRUE : JSOP_FALSE, PN_NULLARY, pos) |
| { } |
| }; |
| |
| class RegExpLiteral : public NullaryNode |
| { |
| public: |
| RegExpLiteral(ObjectBox* reobj, const TokenPos& pos) |
| : NullaryNode(PNK_REGEXP, JSOP_REGEXP, pos) |
| { |
| pn_objbox = reobj; |
| } |
| |
| ObjectBox* objbox() const { return pn_objbox; } |
| |
| static bool test(const ParseNode& node) { |
| bool match = node.isKind(PNK_REGEXP); |
| MOZ_ASSERT_IF(match, node.isArity(PN_NULLARY)); |
| MOZ_ASSERT_IF(match, node.isOp(JSOP_REGEXP)); |
| return match; |
| } |
| }; |
| |
| class PropertyAccess : public ParseNode |
| { |
| public: |
| PropertyAccess(ParseNode* lhs, PropertyName* name, uint32_t begin, uint32_t end) |
| : ParseNode(PNK_DOT, JSOP_NOP, PN_NAME, TokenPos(begin, end)) |
| { |
| MOZ_ASSERT(lhs != nullptr); |
| MOZ_ASSERT(name != nullptr); |
| pn_u.name.expr = lhs; |
| pn_u.name.atom = name; |
| } |
| |
| static bool test(const ParseNode& node) { |
| bool match = node.isKind(PNK_DOT); |
| MOZ_ASSERT_IF(match, node.isArity(PN_NAME)); |
| return match; |
| } |
| |
| ParseNode& expression() const { |
| return *pn_u.name.expr; |
| } |
| |
| PropertyName& name() const { |
| return *pn_u.name.atom->asPropertyName(); |
| } |
| |
| bool isSuper() const { |
| // PNK_SUPERBASE cannot result from any expression syntax. |
| return expression().isKind(PNK_SUPERBASE); |
| } |
| }; |
| |
| class PropertyByValue : public ParseNode |
| { |
| public: |
| PropertyByValue(ParseNode* lhs, ParseNode* propExpr, uint32_t begin, uint32_t end) |
| : ParseNode(PNK_ELEM, JSOP_NOP, PN_BINARY, TokenPos(begin, end)) |
| { |
| pn_u.binary.left = lhs; |
| pn_u.binary.right = propExpr; |
| } |
| |
| static bool test(const ParseNode& node) { |
| bool match = node.isKind(PNK_ELEM); |
| MOZ_ASSERT_IF(match, node.isArity(PN_BINARY)); |
| return match; |
| } |
| |
| bool isSuper() const { |
| return pn_left->isKind(PNK_SUPERBASE); |
| } |
| }; |
| |
| /* |
| * A CallSiteNode represents the implicit call site object argument in a TaggedTemplate. |
| */ |
| struct CallSiteNode : public ListNode { |
| explicit CallSiteNode(uint32_t begin): ListNode(PNK_CALLSITEOBJ, TokenPos(begin, begin + 1)) {} |
| |
| static bool test(const ParseNode& node) { |
| return node.isKind(PNK_CALLSITEOBJ); |
| } |
| |
| bool getRawArrayValue(ExclusiveContext* cx, MutableHandleValue vp) { |
| return pn_head->getConstantValue(cx, AllowObjects, vp); |
| } |
| }; |
| |
| struct ClassMethod : public BinaryNode { |
| /* |
| * Method defintions often keep a name and function body that overlap, |
| * so explicitly define the beginning and end here. |
| */ |
| ClassMethod(ParseNode* name, ParseNode* body, JSOp op, bool isStatic) |
| : BinaryNode(PNK_CLASSMETHOD, op, TokenPos(name->pn_pos.begin, body->pn_pos.end), name, body) |
| { |
| pn_u.binary.isStatic = isStatic; |
| } |
| |
| static bool test(const ParseNode& node) { |
| bool match = node.isKind(PNK_CLASSMETHOD); |
| MOZ_ASSERT_IF(match, node.isArity(PN_BINARY)); |
| return match; |
| } |
| |
| ParseNode& name() const { |
| return *pn_u.binary.left; |
| } |
| ParseNode& method() const { |
| return *pn_u.binary.right; |
| } |
| bool isStatic() const { |
| return pn_u.binary.isStatic; |
| } |
| }; |
| |
| struct ClassNames : public BinaryNode { |
| ClassNames(ParseNode* outerBinding, ParseNode* innerBinding, const TokenPos& pos) |
| : BinaryNode(PNK_CLASSNAMES, JSOP_NOP, pos, outerBinding, innerBinding) |
| { |
| MOZ_ASSERT_IF(outerBinding, outerBinding->isKind(PNK_NAME)); |
| MOZ_ASSERT(innerBinding->isKind(PNK_NAME)); |
| MOZ_ASSERT_IF(outerBinding, innerBinding->pn_atom == outerBinding->pn_atom); |
| } |
| |
| static bool test(const ParseNode& node) { |
| bool match = node.isKind(PNK_CLASSNAMES); |
| MOZ_ASSERT_IF(match, node.isArity(PN_BINARY)); |
| return match; |
| } |
| |
| /* |
| * Classes require two definitions: The first "outer" binding binds the |
| * class into the scope in which it was declared. the outer binding is a |
| * mutable lexial binding. The second "inner" binding binds the class by |
| * name inside a block in which the methods are evaulated. It is immutable, |
| * giving the methods access to the static members of the class even if |
| * the outer binding has been overwritten. |
| */ |
| ParseNode* outerBinding() const { |
| return pn_u.binary.left; |
| } |
| ParseNode* innerBinding() const { |
| return pn_u.binary.right; |
| } |
| }; |
| |
| struct ClassNode : public TernaryNode { |
| ClassNode(ParseNode* names, ParseNode* heritage, ParseNode* methodsOrBlock) |
| : TernaryNode(PNK_CLASS, JSOP_NOP, names, heritage, methodsOrBlock) |
| { |
| MOZ_ASSERT_IF(names, names->is<ClassNames>()); |
| MOZ_ASSERT(methodsOrBlock->is<LexicalScopeNode>() || |
| methodsOrBlock->isKind(PNK_CLASSMETHODLIST)); |
| } |
| |
| static bool test(const ParseNode& node) { |
| bool match = node.isKind(PNK_CLASS); |
| MOZ_ASSERT_IF(match, node.isArity(PN_TERNARY)); |
| return match; |
| } |
| |
| ClassNames* names() const { |
| return pn_kid1 ? &pn_kid1->as<ClassNames>() : nullptr; |
| } |
| ParseNode* heritage() const { |
| return pn_kid2; |
| } |
| ParseNode* methodList() const { |
| if (pn_kid3->isKind(PNK_CLASSMETHODLIST)) |
| return pn_kid3; |
| |
| MOZ_ASSERT(pn_kid3->is<LexicalScopeNode>()); |
| ParseNode* list = pn_kid3->pn_expr; |
| MOZ_ASSERT(list->isKind(PNK_CLASSMETHODLIST)); |
| return list; |
| } |
| ObjectBox* scopeObject() const { |
| MOZ_ASSERT(pn_kid3->is<LexicalScopeNode>()); |
| return pn_kid3->pn_objbox; |
| } |
| }; |
| |
| #ifdef DEBUG |
| void DumpParseTree(ParseNode* pn, int indent = 0); |
| #endif |
| |
| /* |
| * js::Definition is a degenerate subtype of the PN_FUNC and PN_NAME variants |
| * of js::ParseNode, allocated only for function, var, const, and let |
| * declarations that define truly lexical bindings. This means that a child of |
| * a PNK_VAR list may be a Definition as well as a ParseNode. The pn_defn bit |
| * is set for all Definitions, clear otherwise. |
| * |
| * In an upvars list, defn->resolve() is the outermost definition the |
| * name may reference. If a with block or a function that calls eval encloses |
| * the use, the name may end up referring to something else at runtime. |
| * |
| * Note that not all var declarations are definitions: JS allows multiple var |
| * declarations in a function or script, but only the first creates the hoisted |
| * binding. JS programmers do redeclare variables for good refactoring reasons, |
| * for example: |
| * |
| * function foo() { |
| * ... |
| * for (var i ...) ...; |
| * ... |
| * for (var i ...) ...; |
| * ... |
| * } |
| * |
| * Not all definitions bind lexical variables, alas. In global and eval code |
| * var may re-declare a pre-existing property having any attributes, with or |
| * without JSPROP_PERMANENT. In eval code, indeed, ECMA-262 Editions 1 through |
| * 3 require function and var to bind deletable bindings. Global vars thus are |
| * properties of the global object, so they can be aliased even if they can't |
| * be deleted. |
| * |
| * Only bindings within function code may be treated as lexical, of course with |
| * the caveat that hoisting means use before initialization is allowed. We deal |
| * with use before declaration in one pass as follows (error checking elided): |
| * |
| * for (each use of unqualified name x in parse order) { |
| * if (this use of x is a declaration) { |
| * if (x in pc->decls) { // redeclaring |
| * pn = allocate a PN_NAME ParseNode; |
| * } else { // defining |
| * dn = lookup x in pc->lexdeps; |
| * if (dn) // use before def |
| * remove x from pc->lexdeps; |
| * else // def before use |
| * dn = allocate a PN_NAME Definition; |
| * map x to dn via pc->decls; |
| * pn = dn; |
| * } |
| * insert pn into its parent PNK_VAR/PNK_CONST list; |
| * } else { |
| * pn = allocate a ParseNode for this reference to x; |
| * dn = lookup x in pc's lexical scope chain; |
| * if (!dn) { |
| * dn = lookup x in pc->lexdeps; |
| * if (!dn) { |
| * dn = pre-allocate a Definition for x; |
| * map x to dn in pc->lexdeps; |
| * } |
| * } |
| * append pn to dn's use chain; |
| * } |
| * } |
| * |
| * See frontend/BytecodeEmitter.h for js::ParseContext and its top*Stmt, |
| * decls, and lexdeps members. |
| * |
| * Notes: |
| * |
| * 0. To avoid bloating ParseNode, we steal a bit from pn_arity for pn_defn |
| * and set it on a ParseNode instead of allocating a Definition. |
| * |
| * 1. Due to hoisting, a definition cannot be eliminated even if its "Variable |
| * statement" (ECMA-262 12.2) can be proven to be dead code. RecycleTree in |
| * ParseNode.cpp will not recycle a node whose pn_defn bit is set. |
| * |
| * 2. "lookup x in pc's lexical scope chain" gives up on def/use chaining if a |
| * with statement is found along the the scope chain, which includes pc, |
| * pc->parent, etc. Thus we eagerly connect an inner function's use of an |
| * outer's var x if the var x was parsed before the inner function. |
| * |
| * 3. A use may be eliminated as dead by the constant folder, which therefore |
| * must remove the dead name node from its singly-linked use chain, which |
| * would mean hashing to find the definition node and searching to update |
| * the pn_link pointing at the use to be removed. This is costly, so as for |
| * dead definitions, we do not recycle dead pn_used nodes. |
| * |
| * At the end of parsing a function body or global or eval program, pc->lexdeps |
| * holds the lexical dependencies of the parsed unit. The name to def/use chain |
| * mappings are then merged into the parent pc->lexdeps. |
| * |
| * Thus if a later var x is parsed in the outer function satisfying an earlier |
| * inner function's use of x, we will remove dn from pc->lexdeps and re-use it |
| * as the new definition node in the outer function's parse tree. |
| * |
| * When the compiler unwinds from the outermost pc, pc->lexdeps contains the |
| * definition nodes with use chains for all free variables. These are either |
| * global variables or reference errors. |
| */ |
| struct Definition : public ParseNode |
| { |
| bool isFreeVar() const { |
| MOZ_ASSERT(isDefn()); |
| return pn_scopecoord.isFree(); |
| } |
| |
| enum Kind { |
| MISSING = 0, |
| VAR, |
| CONSTANT, |
| LET, |
| ARG, |
| NAMED_LAMBDA, |
| PLACEHOLDER, |
| IMPORT |
| }; |
| |
| bool canHaveInitializer() { return int(kind()) <= int(ARG); } |
| |
| static const char* kindString(Kind kind); |
| |
| Kind kind() { |
| if (getKind() == PNK_FUNCTION) { |
| if (isOp(JSOP_GETARG)) |
| return ARG; |
| return VAR; |
| } |
| MOZ_ASSERT(getKind() == PNK_NAME); |
| if (isOp(JSOP_CALLEE)) |
| return NAMED_LAMBDA; |
| if (isPlaceholder()) |
| return PLACEHOLDER; |
| if (isOp(JSOP_GETARG)) |
| return ARG; |
| if (isImport()) |
| return IMPORT; |
| if (isLexical()) |
| return isConst() ? CONSTANT : LET; |
| return VAR; |
| } |
| }; |
| |
| class ParseNodeAllocator |
| { |
| public: |
| explicit ParseNodeAllocator(ExclusiveContext* cx, LifoAlloc& alloc) |
| : cx(cx), alloc(alloc), freelist(nullptr) |
| {} |
| |
| void* allocNode(); |
| void freeNode(ParseNode* pn); |
| ParseNode* freeTree(ParseNode* pn); |
| void prepareNodeForMutation(ParseNode* pn); |
| |
| private: |
| ExclusiveContext* cx; |
| LifoAlloc& alloc; |
| ParseNode* freelist; |
| }; |
| |
| inline bool |
| ParseNode::test(unsigned flag) const |
| { |
| MOZ_ASSERT(pn_defn || pn_arity == PN_CODE || pn_arity == PN_NAME); |
| #ifdef DEBUG |
| if ((flag & PND_ASSIGNED) && pn_defn && !(pn_dflags & flag)) { |
| for (ParseNode* pn = ((Definition*) this)->dn_uses; pn; pn = pn->pn_link) { |
| MOZ_ASSERT(!pn->pn_defn); |
| MOZ_ASSERT(!(pn->pn_dflags & flag)); |
| } |
| } |
| #endif |
| return !!(pn_dflags & flag); |
| } |
| |
| inline void |
| ParseNode::markAsAssigned() |
| { |
| MOZ_ASSERT(CodeSpec[pn_op].format & JOF_NAME); |
| if (isUsed()) |
| pn_lexdef->pn_dflags |= PND_ASSIGNED; |
| pn_dflags |= PND_ASSIGNED; |
| } |
| |
| inline Definition* |
| ParseNode::resolve() |
| { |
| if (isDefn()) |
| return (Definition*)this; |
| MOZ_ASSERT(lexdef()->isDefn()); |
| return (Definition*)lexdef(); |
| } |
| |
| inline bool |
| ParseNode::isConstant() |
| { |
| switch (pn_type) { |
| case PNK_NUMBER: |
| case PNK_STRING: |
| case PNK_TEMPLATE_STRING: |
| case PNK_NULL: |
| case PNK_FALSE: |
| case PNK_TRUE: |
| return true; |
| case PNK_ARRAY: |
| case PNK_OBJECT: |
| MOZ_ASSERT(isOp(JSOP_NEWINIT)); |
| return !(pn_xflags & PNX_NONCONST); |
| default: |
| return false; |
| } |
| } |
| |
| class ObjectBox |
| { |
| public: |
| JSObject* object; |
| |
| ObjectBox(JSObject* object, ObjectBox* traceLink); |
| bool isFunctionBox() { return object->is<JSFunction>(); } |
| FunctionBox* asFunctionBox(); |
| bool isModuleBox() { return object->is<ModuleObject>(); } |
| ModuleBox* asModuleBox(); |
| virtual void trace(JSTracer* trc); |
| |
| static void TraceList(JSTracer* trc, ObjectBox* listHead); |
| |
| protected: |
| friend struct CGObjectList; |
| |
| ObjectBox* traceLink; |
| ObjectBox* emitLink; |
| |
| ObjectBox(JSFunction* function, ObjectBox* traceLink); |
| }; |
| |
| enum ParseReportKind |
| { |
| ParseError, |
| ParseWarning, |
| ParseExtraWarning, |
| ParseStrictError |
| }; |
| |
| enum FunctionSyntaxKind |
| { |
| Expression, |
| Statement, |
| Arrow, |
| Method, |
| ClassConstructor, |
| DerivedClassConstructor, |
| Getter, |
| GetterNoExpressionClosure, |
| Setter, |
| SetterNoExpressionClosure |
| }; |
| |
| static inline bool |
| IsConstructorKind(FunctionSyntaxKind kind) |
| { |
| return kind == ClassConstructor || kind == DerivedClassConstructor; |
| } |
| |
| static inline bool |
| IsGetterKind(FunctionSyntaxKind kind) |
| { |
| return kind == Getter || kind == GetterNoExpressionClosure; |
| } |
| |
| static inline bool |
| IsSetterKind(FunctionSyntaxKind kind) |
| { |
| return kind == Setter || kind == SetterNoExpressionClosure; |
| } |
| |
| static inline ParseNode* |
| FunctionArgsList(ParseNode* fn, unsigned* numFormals) |
| { |
| MOZ_ASSERT(fn->isKind(PNK_FUNCTION)); |
| ParseNode* argsBody = fn->pn_body; |
| MOZ_ASSERT(argsBody->isKind(PNK_ARGSBODY)); |
| *numFormals = argsBody->pn_count; |
| if (*numFormals > 0 && argsBody->last()->isKind(PNK_STATEMENTLIST)) |
| (*numFormals)--; |
| MOZ_ASSERT(argsBody->isArity(PN_LIST)); |
| return argsBody->pn_head; |
| } |
| |
| } /* namespace frontend */ |
| } /* namespace js */ |
| |
| #endif /* frontend_ParseNode_h */ |