/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80: * 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_SyntaxParseHandler_h #define frontend_SyntaxParseHandler_h #include "mozilla/Assertions.h" #include "mozilla/Attributes.h" #include "mozilla/Maybe.h" // mozilla::Maybe #include #include "frontend/FunctionSyntaxKind.h" // FunctionSyntaxKind #include "frontend/NameAnalysisTypes.h" // PrivateNameKind #include "frontend/ParseNode.h" #include "frontend/TokenStream.h" #include "js/GCAnnotations.h" #include "vm/JSContext.h" namespace js { namespace frontend { // Parse handler used when processing the syntax in a block of code, to generate // the minimal information which is required to detect syntax errors and allow // bytecode to be emitted for outer functions. // // When parsing, we start at the top level with a full parse, and when possible // only check the syntax for inner functions, so that they can be lazily parsed // into bytecode when/if they first run. Checking the syntax of a function is // several times faster than doing a full parse/emit, and lazy parsing improves // both performance and memory usage significantly when pages contain large // amounts of code that never executes (which happens often). class SyntaxParseHandler { // Remember the last encountered name or string literal during syntax parses. const ParserAtom* lastAtom; TokenPos lastStringPos; public: enum Node { NodeFailure = 0, NodeGeneric, NodeGetProp, NodeStringExprStatement, NodeReturn, NodeBreak, NodeThrow, NodeEmptyStatement, NodeVarDeclaration, NodeLexicalDeclaration, // A non-arrow function expression with block body, from bog-standard // ECMAScript. NodeFunctionExpression, NodeFunctionArrow, NodeFunctionStatement, // This is needed for proper assignment-target handling. ES6 formally // requires function calls *not* pass IsValidSimpleAssignmentTarget, // but at last check there were still sites with |f() = 5| and similar // in code not actually executed (or at least not executed enough to be // noticed). NodeFunctionCall, NodeOptionalFunctionCall, // Node representing normal names which don't require any special // casing. NodeName, // Nodes representing the names "arguments" and "eval". NodeArgumentsName, NodeEvalName, // Node representing the "async" name, which may actually be a // contextual keyword. NodePotentialAsyncKeyword, // Node representing private names. NodePrivateName, NodeDottedProperty, NodeOptionalDottedProperty, NodeElement, NodeOptionalElement, // A distinct node for [PrivateName], to make detecting delete this.#x // detectable in syntax parse NodePrivateElement, // Destructuring target patterns can't be parenthesized: |([a]) = [3];| // must be a syntax error. (We can't use NodeGeneric instead of these // because that would trigger invalid-left-hand-side ReferenceError // semantics when SyntaxError semantics are desired.) NodeParenthesizedArray, NodeParenthesizedObject, // In rare cases a parenthesized |node| doesn't have the same semantics // as |node|. Each such node has a special Node value, and we use a // different Node value to represent the parenthesized form. See also // is{Unp,P}arenthesized*(Node), parenthesize(Node), and the various // functions that deal in NodeUnparenthesized* below. // Valuable for recognizing potential destructuring patterns. NodeUnparenthesizedArray, NodeUnparenthesizedObject, // The directive prologue at the start of a FunctionBody or ScriptBody // is the longest sequence (possibly empty) of string literal // expression statements at the start of a function. Thus we need this // to treat |"use strict";| as a possible Use Strict Directive and // |("use strict");| as a useless statement. NodeUnparenthesizedString, // For destructuring patterns an assignment element with // an initializer expression is not allowed be parenthesized. // i.e. |{x = 1} = obj| NodeUnparenthesizedAssignment, // This node is necessary to determine if the base operand in an // exponentiation operation is an unparenthesized unary expression. // We want to reject |-2 ** 3|, but still need to allow |(-2) ** 3|. NodeUnparenthesizedUnary, // This node is necessary to determine if the LHS of a property access is // super related. NodeSuperBase }; #define DECLARE_TYPE(typeName, longTypeName, asMethodName) \ using longTypeName = Node; FOR_EACH_PARSENODE_SUBCLASS(DECLARE_TYPE) #undef DECLARE_TYPE using NullNode = Node; bool isNonArrowFunctionExpression(Node node) const { return node == NodeFunctionExpression; } bool isPropertyAccess(Node node) { return node == NodeDottedProperty || node == NodeElement || node == NodePrivateElement; } bool isOptionalPropertyAccess(Node node) { return node == NodeOptionalDottedProperty || node == NodeOptionalElement; } bool isFunctionCall(Node node) { // Note: super() is a special form, *not* a function call. return node == NodeFunctionCall; } static bool isUnparenthesizedDestructuringPattern(Node node) { return node == NodeUnparenthesizedArray || node == NodeUnparenthesizedObject; } static bool isParenthesizedDestructuringPattern(Node node) { // Technically this isn't a destructuring target at all -- the grammar // doesn't treat it as such. But we need to know when this happens to // consider it a SyntaxError rather than an invalid-left-hand-side // ReferenceError. return node == NodeParenthesizedArray || node == NodeParenthesizedObject; } public: SyntaxParseHandler(JSContext* cx, LifoAlloc& alloc, BaseScript* lazyOuterFunction) : lastAtom(nullptr) {} static NullNode null() { return NodeFailure; } #define DECLARE_AS(typeName, longTypeName, asMethodName) \ static longTypeName asMethodName(Node node) { return node; } FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) #undef DECLARE_AS NameNodeType newName(const ParserName* name, const TokenPos& pos, JSContext* cx) { lastAtom = name; if (name == cx->parserNames().arguments) { return NodeArgumentsName; } if (pos.begin + strlen("async") == pos.end && name == cx->parserNames().async) { return NodePotentialAsyncKeyword; } if (name == cx->parserNames().eval) { return NodeEvalName; } return NodeName; } UnaryNodeType newComputedName(Node expr, uint32_t start, uint32_t end) { return NodeGeneric; } UnaryNodeType newSyntheticComputedName(Node expr, uint32_t start, uint32_t end) { return NodeGeneric; } NameNodeType newObjectLiteralPropertyName(const ParserAtom* atom, const TokenPos& pos) { return NodeName; } NameNodeType newPrivateName(const ParserAtom* atom, const TokenPos& pos) { return NodePrivateName; } NumericLiteralType newNumber(double value, DecimalPoint decimalPoint, const TokenPos& pos) { return NodeGeneric; } BigIntLiteralType newBigInt() { return NodeGeneric; } BooleanLiteralType newBooleanLiteral(bool cond, const TokenPos& pos) { return NodeGeneric; } NameNodeType newStringLiteral(const ParserAtom* atom, const TokenPos& pos) { lastAtom = atom; lastStringPos = pos; return NodeUnparenthesizedString; } NameNodeType newTemplateStringLiteral(const ParserAtom* atom, const TokenPos& pos) { return NodeGeneric; } CallSiteNodeType newCallSiteObject(uint32_t begin) { return NodeGeneric; } void addToCallSiteObject(CallSiteNodeType callSiteObj, Node rawNode, Node cookedNode) {} ThisLiteralType newThisLiteral(const TokenPos& pos, Node thisName) { return NodeGeneric; } NullLiteralType newNullLiteral(const TokenPos& pos) { return NodeGeneric; } RawUndefinedLiteralType newRawUndefinedLiteral(const TokenPos& pos) { return NodeGeneric; } RegExpLiteralType newRegExp(Node reobj, const TokenPos& pos) { return NodeGeneric; } ConditionalExpressionType newConditional(Node cond, Node thenExpr, Node elseExpr) { return NodeGeneric; } Node newElision() { return NodeGeneric; } UnaryNodeType newDelete(uint32_t begin, Node expr) { return NodeUnparenthesizedUnary; } UnaryNodeType newTypeof(uint32_t begin, Node kid) { return NodeUnparenthesizedUnary; } UnaryNodeType newUnary(ParseNodeKind kind, uint32_t begin, Node kid) { return NodeUnparenthesizedUnary; } UnaryNodeType newUpdate(ParseNodeKind kind, uint32_t begin, Node kid) { return NodeGeneric; } UnaryNodeType newSpread(uint32_t begin, Node kid) { return NodeGeneric; } Node appendOrCreateList(ParseNodeKind kind, Node left, Node right, ParseContext* pc) { return NodeGeneric; } // Expressions ListNodeType newArrayLiteral(uint32_t begin) { return NodeUnparenthesizedArray; } MOZ_MUST_USE bool addElision(ListNodeType literal, const TokenPos& pos) { return true; } MOZ_MUST_USE bool addSpreadElement(ListNodeType literal, uint32_t begin, Node inner) { return true; } void addArrayElement(ListNodeType literal, Node element) {} ListNodeType newArguments(const TokenPos& pos) { return NodeGeneric; } CallNodeType newCall(Node callee, Node args, JSOp callOp) { return NodeFunctionCall; } CallNodeType newOptionalCall(Node callee, Node args, JSOp callOp) { return NodeOptionalFunctionCall; } CallNodeType newSuperCall(Node callee, Node args, bool isSpread) { return NodeGeneric; } CallNodeType newTaggedTemplate(Node tag, Node args, JSOp callOp) { return NodeGeneric; } ListNodeType newObjectLiteral(uint32_t begin) { return NodeUnparenthesizedObject; } ListNodeType newClassMemberList(uint32_t begin) { return NodeGeneric; } ClassNamesType newClassNames(Node outer, Node inner, const TokenPos& pos) { return NodeGeneric; } ClassNodeType newClass(Node name, Node heritage, Node methodBlock, const TokenPos& pos) { return NodeGeneric; } LexicalScopeNodeType newLexicalScope(Node body) { return NodeLexicalDeclaration; } BinaryNodeType newNewTarget(NullaryNodeType newHolder, NullaryNodeType targetHolder) { return NodeGeneric; } NullaryNodeType newPosHolder(const TokenPos& pos) { return NodeGeneric; } UnaryNodeType newSuperBase(Node thisName, const TokenPos& pos) { return NodeSuperBase; } MOZ_MUST_USE bool addPrototypeMutation(ListNodeType literal, uint32_t begin, Node expr) { return true; } BinaryNodeType newPropertyDefinition(Node key, Node val) { return NodeGeneric; } void addPropertyDefinition(ListNodeType literal, BinaryNodeType propdef) {} MOZ_MUST_USE bool addPropertyDefinition(ListNodeType literal, Node key, Node expr) { return true; } MOZ_MUST_USE bool addShorthand(ListNodeType literal, NameNodeType name, NameNodeType expr) { return true; } MOZ_MUST_USE bool addSpreadProperty(ListNodeType literal, uint32_t begin, Node inner) { return true; } MOZ_MUST_USE bool addObjectMethodDefinition(ListNodeType literal, Node key, FunctionNodeType funNode, AccessorType atype) { return true; } MOZ_MUST_USE Node newClassMethodDefinition( Node key, FunctionNodeType funNode, AccessorType atype, bool isStatic, mozilla::Maybe initializerIfPrivate) { return NodeGeneric; } MOZ_MUST_USE Node newClassFieldDefinition(Node name, FunctionNodeType initializer, bool isStatic) { return NodeGeneric; } MOZ_MUST_USE bool addClassMemberDefinition(ListNodeType memberList, Node member) { return true; } UnaryNodeType newYieldExpression(uint32_t begin, Node value) { return NodeGeneric; } UnaryNodeType newYieldStarExpression(uint32_t begin, Node value) { return NodeGeneric; } UnaryNodeType newAwaitExpression(uint32_t begin, Node value) { return NodeUnparenthesizedUnary; } UnaryNodeType newOptionalChain(uint32_t begin, Node value) { return NodeGeneric; } // Statements ListNodeType newStatementList(const TokenPos& pos) { return NodeGeneric; } void addStatementToList(ListNodeType list, Node stmt) {} void setListEndPosition(ListNodeType list, const TokenPos& pos) {} void addCaseStatementToList(ListNodeType list, CaseClauseType caseClause) {} MOZ_MUST_USE bool prependInitialYield(ListNodeType stmtList, Node genName) { return true; } NullaryNodeType newEmptyStatement(const TokenPos& pos) { return NodeEmptyStatement; } UnaryNodeType newExportDeclaration(Node kid, const TokenPos& pos) { return NodeGeneric; } BinaryNodeType newExportFromDeclaration(uint32_t begin, Node exportSpecSet, Node moduleSpec) { return NodeGeneric; } BinaryNodeType newExportDefaultDeclaration(Node kid, Node maybeBinding, const TokenPos& pos) { return NodeGeneric; } BinaryNodeType newExportSpec(Node bindingName, Node exportName) { return NodeGeneric; } NullaryNodeType newExportBatchSpec(const TokenPos& pos) { return NodeGeneric; } BinaryNodeType newImportMeta(NullaryNodeType importHolder, NullaryNodeType metaHolder) { return NodeGeneric; } BinaryNodeType newCallImport(NullaryNodeType importHolder, Node singleArg) { return NodeGeneric; } BinaryNodeType newSetThis(Node thisName, Node value) { return value; } UnaryNodeType newExprStatement(Node expr, uint32_t end) { return expr == NodeUnparenthesizedString ? NodeStringExprStatement : NodeGeneric; } TernaryNodeType newIfStatement(uint32_t begin, Node cond, Node thenBranch, Node elseBranch) { return NodeGeneric; } BinaryNodeType newDoWhileStatement(Node body, Node cond, const TokenPos& pos) { return NodeGeneric; } BinaryNodeType newWhileStatement(uint32_t begin, Node cond, Node body) { return NodeGeneric; } SwitchStatementType newSwitchStatement( uint32_t begin, Node discriminant, LexicalScopeNodeType lexicalForCaseList, bool hasDefault) { return NodeGeneric; } CaseClauseType newCaseOrDefault(uint32_t begin, Node expr, Node body) { return NodeGeneric; } ContinueStatementType newContinueStatement(const ParserName* label, const TokenPos& pos) { return NodeGeneric; } BreakStatementType newBreakStatement(const ParserName* label, const TokenPos& pos) { return NodeBreak; } UnaryNodeType newReturnStatement(Node expr, const TokenPos& pos) { return NodeReturn; } UnaryNodeType newExpressionBody(Node expr) { return NodeReturn; } BinaryNodeType newWithStatement(uint32_t begin, Node expr, Node body) { return NodeGeneric; } LabeledStatementType newLabeledStatement(const ParserName* label, Node stmt, uint32_t begin) { return NodeGeneric; } UnaryNodeType newThrowStatement(Node expr, const TokenPos& pos) { return NodeThrow; } TernaryNodeType newTryStatement(uint32_t begin, Node body, LexicalScopeNodeType catchScope, Node finallyBlock) { return NodeGeneric; } DebuggerStatementType newDebuggerStatement(const TokenPos& pos) { return NodeGeneric; } NameNodeType newPropertyName(const ParserName* name, const TokenPos& pos) { lastAtom = name; return NodeGeneric; } PropertyAccessType newPropertyAccess(Node expr, NameNodeType key) { return NodeDottedProperty; } PropertyAccessType newOptionalPropertyAccess(Node expr, NameNodeType key) { return NodeOptionalDottedProperty; } PropertyByValueType newPropertyByValue(Node lhs, Node index, uint32_t end) { if (isPrivateName(index)) { return NodePrivateElement; } return NodeElement; } PropertyByValueType newOptionalPropertyByValue(Node lhs, Node index, uint32_t end) { return NodeOptionalElement; } MOZ_MUST_USE bool setupCatchScope(LexicalScopeNodeType lexicalScope, Node catchName, Node catchBody) { return true; } MOZ_MUST_USE bool setLastFunctionFormalParameterDefault( FunctionNodeType funNode, Node defaultValue) { return true; } void checkAndSetIsDirectRHSAnonFunction(Node pn) {} FunctionNodeType newFunction(FunctionSyntaxKind syntaxKind, const TokenPos& pos) { switch (syntaxKind) { case FunctionSyntaxKind::Statement: return NodeFunctionStatement; case FunctionSyntaxKind::Arrow: return NodeFunctionArrow; default: // All non-arrow function expressions are initially presumed to have // block body. This will be overridden later *if* the function // expression permissibly has an AssignmentExpression body. return NodeFunctionExpression; } } void setFunctionFormalParametersAndBody(FunctionNodeType funNode, ListNodeType paramsBody) {} void setFunctionBody(FunctionNodeType funNode, LexicalScopeNodeType body) {} void setFunctionBox(FunctionNodeType funNode, FunctionBox* funbox) {} void addFunctionFormalParameter(FunctionNodeType funNode, Node argpn) {} ForNodeType newForStatement(uint32_t begin, TernaryNodeType forHead, Node body, unsigned iflags) { return NodeGeneric; } TernaryNodeType newForHead(Node init, Node test, Node update, const TokenPos& pos) { return NodeGeneric; } TernaryNodeType newForInOrOfHead(ParseNodeKind kind, Node target, Node iteratedExpr, const TokenPos& pos) { return NodeGeneric; } AssignmentNodeType finishInitializerAssignment(NameNodeType nameNode, Node init) { return NodeUnparenthesizedAssignment; } void setBeginPosition(Node pn, Node oth) {} void setBeginPosition(Node pn, uint32_t begin) {} void setEndPosition(Node pn, Node oth) {} void setEndPosition(Node pn, uint32_t end) {} uint32_t getFunctionNameOffset(Node func, TokenStreamAnyChars& ts) { // XXX This offset isn't relevant to the offending function name. But // we may not *have* that function name around, because of how lazy // parsing works -- the actual name could be outside // |tokenStream.userbuf|'s observed region. So the current offset // is the best we can do. return ts.currentToken().pos.begin; } ListNodeType newList(ParseNodeKind kind, const TokenPos& pos) { MOZ_ASSERT(kind != ParseNodeKind::VarStmt); MOZ_ASSERT(kind != ParseNodeKind::LetDecl); MOZ_ASSERT(kind != ParseNodeKind::ConstDecl); return NodeGeneric; } ListNodeType newList(ParseNodeKind kind, Node kid) { return newList(kind, TokenPos()); } ListNodeType newDeclarationList(ParseNodeKind kind, const TokenPos& pos) { if (kind == ParseNodeKind::VarStmt) { return NodeVarDeclaration; } MOZ_ASSERT(kind == ParseNodeKind::LetDecl || kind == ParseNodeKind::ConstDecl); return NodeLexicalDeclaration; } bool isDeclarationList(Node node) { return node == NodeVarDeclaration || node == NodeLexicalDeclaration; } // This method should only be called from parsers using FullParseHandler. Node singleBindingFromDeclaration(ListNodeType decl) = delete; ListNodeType newCommaExpressionList(Node kid) { return NodeGeneric; } void addList(ListNodeType list, Node kid) { MOZ_ASSERT(list == NodeGeneric || list == NodeUnparenthesizedArray || list == NodeUnparenthesizedObject || list == NodeVarDeclaration || list == NodeLexicalDeclaration || list == NodeFunctionCall); } CallNodeType newNewExpression(uint32_t begin, Node ctor, Node args, bool isSpread) { return NodeGeneric; } AssignmentNodeType newAssignment(ParseNodeKind kind, Node lhs, Node rhs) { return kind == ParseNodeKind::AssignExpr ? NodeUnparenthesizedAssignment : NodeGeneric; } bool isUnparenthesizedAssignment(Node node) { return node == NodeUnparenthesizedAssignment; } bool isUnparenthesizedUnaryExpression(Node node) { return node == NodeUnparenthesizedUnary; } bool isReturnStatement(Node node) { return node == NodeReturn; } bool isStatementPermittedAfterReturnStatement(Node pn) { return pn == NodeFunctionStatement || isNonArrowFunctionExpression(pn) || pn == NodeVarDeclaration || pn == NodeBreak || pn == NodeThrow || pn == NodeEmptyStatement; } bool isSuperBase(Node pn) { return pn == NodeSuperBase; } void setListHasNonConstInitializer(ListNodeType literal) {} MOZ_MUST_USE Node parenthesize(Node node) { // A number of nodes have different behavior upon parenthesization, but // only in some circumstances. Convert these nodes to special // parenthesized forms. if (node == NodeUnparenthesizedArray) { return NodeParenthesizedArray; } if (node == NodeUnparenthesizedObject) { return NodeParenthesizedObject; } // Other nodes need not be recognizable after parenthesization; convert // them to a generic node. if (node == NodeUnparenthesizedString || node == NodeUnparenthesizedAssignment || node == NodeUnparenthesizedUnary) { return NodeGeneric; } // Convert parenthesized |async| to a normal name node. if (node == NodePotentialAsyncKeyword) { return NodeName; } // In all other cases, the parenthesized form of |node| is equivalent // to the unparenthesized form: return |node| unchanged. return node; } template MOZ_MUST_USE NodeType setLikelyIIFE(NodeType node) { return node; // Remain in syntax-parse mode. } bool isName(Node node) { return node == NodeName || node == NodeArgumentsName || node == NodeEvalName || node == NodePotentialAsyncKeyword; } bool isArgumentsName(Node node, JSContext* cx) { return node == NodeArgumentsName; } bool isEvalName(Node node, JSContext* cx) { return node == NodeEvalName; } bool isAsyncKeyword(Node node, JSContext* cx) { return node == NodePotentialAsyncKeyword; } bool isPrivateName(Node node) { return node == NodePrivateName; } bool isPrivateField(Node node) { return node == NodePrivateElement; } const ParserName* maybeDottedProperty(Node node) { // Note: |super.apply(...)| is a special form that calls an "apply" // method retrieved from one value, but using a *different* value as // |this|. It's not really eligible for the funapply/funcall // optimizations as they're currently implemented (assuming a single // value is used for both retrieval and |this|). if (node != NodeDottedProperty && node != NodeOptionalDottedProperty) { return nullptr; } return lastAtom->asName(); } const ParserAtom* isStringExprStatement(Node pn, TokenPos* pos) { if (pn == NodeStringExprStatement) { *pos = lastStringPos; return lastAtom; } return nullptr; } bool canSkipLazyInnerFunctions() { return false; } bool canSkipLazyClosedOverBindings() { return false; } JSAtom* nextLazyClosedOverBinding() { MOZ_CRASH( "SyntaxParseHandler::canSkipLazyClosedOverBindings must return false"); } void setPrivateNameKind(Node node, PrivateNameKind kind) {} }; } // namespace frontend } // namespace js #endif /* frontend_SyntaxParseHandler_h */