/* -*- 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 "builtin/ModuleObject.h" #include "frontend/TokenStream.h" namespace js { namespace frontend { class ParseContext; class FullParseHandler; class FunctionBox; class ObjectBox; #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(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(PARAMSBODY) \ 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) \ F(AWAIT) \ \ /* \ * 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 * > so that code generation can emit a left-associative branch * around 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; } inline bool IsTypeofKind(ParseNodeKind kind) { return PNK_TYPEOFNAME <= kind && kind <= PNK_TYPEOFEXPR; } /* * Label Variant Members * ----- ------- ------- * * 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_PARAMSBODY, ordinarily; * PNK_LEXICALSCOPE for implicit function in genexpr * PNK_PARAMSBODY 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_PARAMSBODY or PNK_STATEMENTLIST node * PNK_SPREAD unary pn_kid: expression being spread * * * 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: declaration or expression to left of 'in' * pn_kid2: null * pn_kid3: object expr to right of 'in' * PNK_FOROF ternary pn_kid1: declaration or expression to left of 'of' * pn_kid2: null * 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 pn_left: head expr; pn_right: body; * PNK_VAR, list pn_head: list of PNK_NAME or PNK_ASSIGN nodes * PNK_LET, each name node has either * PNK_CONST 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 * * * 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_AWAIT * 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_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 * 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_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 scope pn_u.scope.bindings: scope bindings * pn_u.scope.body: scope 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_TERNARY, /* three kids */ PN_CODE, /* module or function definition node */ PN_LIST, /* generic singly linked list */ PN_NAME, /* name, label, or regexp */ PN_SCOPE /* lexical scope */ }; class LoopControlStatement; class BreakStatement; class ContinueStatement; class ConditionalExpression; class PropertyAccess; class ParseNode { uint16_t pn_type; /* PNK_* type */ uint8_t pn_op; /* see JSOp enum and jsopcode.tbl */ uint8_t pn_arity:4; /* see ParseNodeArity enum */ bool pn_parens:1; /* this expr was enclosed in parens */ bool pn_rhs_anon_fun:1; /* this expr is anonymous function or class that * is a direct RHS of PNK_ASSIGN or PNK_COLON of * property, that needs SetFunctionName. */ 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(false), pn_rhs_anon_fun(false), pn_pos(0, 0), pn_next(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(false), pn_rhs_anon_fun(false), pn_pos(pos), pn_next(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 isDirectRHSAnonFunction() const { return pn_rhs_anon_fun; } void setDirectRHSAnonFunction(bool enabled) { pn_rhs_anon_fun = enabled; } TokenPos pn_pos; /* two 16-bit pairs here, for 64 bits */ ParseNode* pn_next; /* intrinsic link in parent PN_LIST */ 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; /* see PNX_* 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 */ 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; /* regexp object */ FunctionBox* funbox; /* function object */ }; ParseNode* expr; /* module or function body, var initializer, argument default, or base object of PNK_DOT */ } name; struct { LexicalScope::Data* bindings; ParseNode* body; } scope; 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_objbox pn_u.name.objbox #define pn_funbox pn_u.name.funbox #define pn_body pn_u.name.expr #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_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_dval pn_u.number.value 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* pc); inline PropertyName* name() const; inline JSAtom* atom() const; ParseNode* expr() const { MOZ_ASSERT(pn_arity == PN_NAME || pn_arity == PN_CODE); return pn_expr; } bool isEmptyScope() const { MOZ_ASSERT(pn_arity == PN_SCOPE); return !pn_u.scope.bindings; } Handle scopeBindings() const { MOZ_ASSERT(!isEmptyScope()); // Bindings' GC safety depend on the presence of an AutoKeepAtoms that // the rest of the frontend also depends on. return Handle::fromMarkedLocation(&pn_u.scope.bindings); } ParseNode* scopeBody() const { MOZ_ASSERT(pn_arity == PN_SCOPE); return pn_u.scope.body; } void setScopeBody(ParseNode* body) { MOZ_ASSERT(pn_arity == PN_SCOPE); pn_u.scope.body = body; } /* PN_LIST pn_xflags bits. */ #define PNX_FUNCDEFS 0x01 /* contains top-level function statements */ #define PNX_ARRAYHOLESPREAD 0x02 /* one or more of 1. array initialiser has holes 2. array initializer has spread node */ #define PNX_NONCONST 0x04 /* initialiser has non-constants */ 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 isOp(JSOP_INITLEXICAL)); // block-level function stmt 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; } /* 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; } // True iff this is a for-in/of loop variable declaration (var/let/const). bool isForLoopDeclaration() const { if (isKind(PNK_VAR) || isKind(PNK_LET) || isKind(PNK_CONST)) { MOZ_ASSERT(isArity(PN_LIST)); MOZ_ASSERT(pn_count > 0); return true; } return false; } ParseNode* generatorExpr() const { MOZ_ASSERT(isKind(PNK_GENEXP)); ParseNode* callee = this->pn_head; MOZ_ASSERT(callee->isKind(PNK_FUNCTION)); ParseNode* paramsBody = callee->pn_body; MOZ_ASSERT(paramsBody->isKind(PNK_PARAMSBODY)); ParseNode* body = paramsBody->last(); MOZ_ASSERT(body->isKind(PNK_STATEMENTLIST)); MOZ_ASSERT(body->last()->isKind(PNK_LEXICALSCOPE) || body->last()->isKind(PNK_COMPREHENSIONFOR)); return body->last(); } /* * 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; } 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; } 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 }; MOZ_MUST_USE bool getConstantValue(ExclusiveContext* cx, AllowConstantObjects allowObjects, MutableHandleValue vp, Value* compare = nullptr, size_t ncompare = 0, NewObjectKind newKind = TenuredObject); inline bool isConstant(); template inline bool is() const { return NodeType::test(*this); } /* Casting operations. */ template inline NodeType& as() { MOZ_ASSERT(NodeType::test(*this)); return *static_cast(this); } template inline const NodeType& as() const { MOZ_ASSERT(NodeType::test(*this)); return *static_cast(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 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, JSOp op, const TokenPos& pos) : ParseNode(kind, op, PN_CODE, pos) { MOZ_ASSERT(kind == PNK_FUNCTION || kind == PNK_MODULE); MOZ_ASSERT_IF(kind == PNK_MODULE, op == JSOP_NOP); MOZ_ASSERT(op == JSOP_NOP || // statement, module op == JSOP_LAMBDA_ARROW || // arrow function op == JSOP_LAMBDA); // expression, method, comprehension, accessor, &c. MOZ_ASSERT(!pn_body); MOZ_ASSERT(!pn_objbox); } public: #ifdef DEBUG void dump(int indent); #endif }; struct NameNode : public ParseNode { NameNode(ParseNodeKind kind, JSOp op, JSAtom* atom, const TokenPos& pos) : ParseNode(kind, op, PN_NAME, pos) { pn_atom = atom; pn_expr = nullptr; } #ifdef DEBUG void dump(int indent); #endif }; struct LexicalScopeNode : public ParseNode { LexicalScopeNode(LexicalScope::Data* bindings, ParseNode* body) : ParseNode(PNK_LEXICALSCOPE, JSOP_NOP, PN_SCOPE, body->pn_pos) { pn_u.scope.bindings = bindings; pn_u.scope.body = body; } static bool test(const ParseNode& node) { return node.isKind(PNK_LEXICALSCOPE); } #ifdef DEBUG void dump(int indent); #endif }; 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() : 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); } MOZ_MUST_USE 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()); MOZ_ASSERT(methodsOrBlock->is() || 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() : nullptr; } ParseNode* heritage() const { return pn_kid2; } ParseNode* methodList() const { if (pn_kid3->isKind(PNK_CLASSMETHODLIST)) return pn_kid3; MOZ_ASSERT(pn_kid3->is()); ParseNode* list = pn_kid3->scopeBody(); MOZ_ASSERT(list->isKind(PNK_CLASSMETHODLIST)); return list; } Handle scopeBindings() const { MOZ_ASSERT(pn_kid3->is()); return pn_kid3->scopeBindings(); } }; #ifdef DEBUG void DumpParseTree(ParseNode* pn, int indent = 0); #endif 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::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(); } FunctionBox* asFunctionBox(); 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 bool IsMethodDefinitionKind(FunctionSyntaxKind kind) { return kind == Method || IsConstructorKind(kind) || IsGetterKind(kind) || IsSetterKind(kind); } static inline ParseNode* FunctionFormalParametersList(ParseNode* fn, unsigned* numFormals) { MOZ_ASSERT(fn->isKind(PNK_FUNCTION)); ParseNode* argsBody = fn->pn_body; MOZ_ASSERT(argsBody->isKind(PNK_PARAMSBODY)); *numFormals = argsBody->pn_count; if (*numFormals > 0 && argsBody->last()->isKind(PNK_LEXICALSCOPE) && argsBody->last()->scopeBody()->isKind(PNK_STATEMENTLIST)) { (*numFormals)--; } MOZ_ASSERT(argsBody->isArity(PN_LIST)); return argsBody->pn_head; } bool IsAnonymousFunctionDefinition(ParseNode* pn); } /* namespace frontend */ } /* namespace js */ #endif /* frontend_ParseNode_h */