blob: 3785ffd181d1e2360771904a59e51e06fb1bc7ff [file] [log] [blame]
/* -*- 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_SyntaxParseHandler_h
#define frontend_SyntaxParseHandler_h
#include "mozilla/Attributes.h"
#include "frontend/ParseNode.h"
#include "frontend/TokenStream.h"
namespace js {
namespace frontend {
template <typename ParseHandler>
class Parser;
// 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.
JSAtom* lastAtom;
TokenPos lastStringPos;
TokenStream& tokenStream;
public:
enum Node {
NodeFailure = 0,
NodeGeneric,
NodeGetProp,
NodeStringExprStatement,
NodeReturn,
NodeHoistableDeclaration,
NodeBreak,
NodeThrow,
NodeEmptyStatement,
// 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,
// Nodes representing *parenthesized* IsValidSimpleAssignmentTarget
// nodes. We can't simply treat all such parenthesized nodes
// identically, because in assignment and increment/decrement contexts
// ES6 says that parentheses constitute a syntax error.
//
// var obj = {};
// var val;
// (val) = 3; (obj.prop) = 4; // okay per ES5's little mind
// [(a)] = [3]; [(obj.prop)] = [4]; // invalid ES6 syntax
// // ...and so on for the other IsValidSimpleAssignmentTarget nodes
//
// We don't know in advance in the current parser when we're parsing
// in a place where name parenthesization changes meaning, so we must
// have multiple node values for these cases.
NodeParenthesizedArgumentsName,
NodeParenthesizedEvalName,
NodeParenthesizedName,
NodeDottedProperty,
NodeElement,
// 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.
// Nodes representing unparenthesized names.
NodeUnparenthesizedArgumentsName,
NodeUnparenthesizedEvalName,
NodeUnparenthesizedName,
// 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,
// Legacy generator expressions of the form |(expr for (...))| and
// array comprehensions of the form |[expr for (...)]|) don't permit
// |expr| to be a comma expression. Thus we need this to treat
// |(a(), b for (x in []))| as a syntax error and
// |((a(), b) for (x in []))| as a generator that calls |a| and then
// yields |b| each time it's resumed.
NodeUnparenthesizedCommaExpr,
// Yield expressions currently (but not in ES6 -- a SpiderMonkey bug to
// fix) must generally be parenthesized. (See the uses of
// isUnparenthesizedYieldExpression in Parser.cpp for the rare
// exceptions.) Thus we need this to treat |yield 1, 2;| as a syntax
// error and |(yield 1), 2;| as a comma expression that will yield 1,
// then evaluate to 2.
NodeUnparenthesizedYieldExpr,
// Assignment expressions in condition contexts could be typos for
// equality checks. (Think |if (x = y)| versus |if (x == y)|.) Thus
// we need this to treat |if (x = y)| as a possible typo and
// |if ((x = y))| as a deliberate assignment within a condition.
//
// (Technically this isn't needed, as these are *only* extraWarnings
// warnings, and parsing with that option disables syntax parsing. But
// it seems best to be consistent, and perhaps the syntax parser will
// eventually enforce extraWarnings and will require this then.)
NodeUnparenthesizedAssignment,
// This node is necessary to determine if the LHS of a property access is
// super related.
NodeSuperBase
};
typedef Definition::Kind DefinitionNode;
bool isPropertyAccess(Node node) {
return node == NodeDottedProperty || node == NodeElement;
}
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;
}
static bool isDestructuringPatternAnyParentheses(Node node) {
return isUnparenthesizedDestructuringPattern(node) ||
isParenthesizedDestructuringPattern(node);
}
public:
SyntaxParseHandler(ExclusiveContext* cx, LifoAlloc& alloc,
TokenStream& tokenStream, Parser<SyntaxParseHandler>* syntaxParser,
LazyScript* lazyOuterFunction)
: lastAtom(nullptr),
tokenStream(tokenStream)
{}
static Node null() { return NodeFailure; }
void prepareNodeForMutation(Node node) {}
void freeTree(Node node) {}
void trace(JSTracer* trc) {}
Node newName(PropertyName* name, uint32_t blockid, const TokenPos& pos, ExclusiveContext* cx) {
lastAtom = name;
if (name == cx->names().arguments)
return NodeUnparenthesizedArgumentsName;
if (name == cx->names().eval)
return NodeUnparenthesizedEvalName;
return NodeUnparenthesizedName;
}
Node newComputedName(Node expr, uint32_t start, uint32_t end) {
return NodeGeneric;
}
DefinitionNode newPlaceholder(JSAtom* atom, uint32_t blockid, const TokenPos& pos) {
return Definition::PLACEHOLDER;
}
Node newObjectLiteralPropertyName(JSAtom* atom, const TokenPos& pos) {
return NodeUnparenthesizedName;
}
Node newNumber(double value, DecimalPoint decimalPoint, const TokenPos& pos) { return NodeGeneric; }
Node newBooleanLiteral(bool cond, const TokenPos& pos) { return NodeGeneric; }
Node newStringLiteral(JSAtom* atom, const TokenPos& pos) {
lastAtom = atom;
lastStringPos = pos;
return NodeUnparenthesizedString;
}
Node newTemplateStringLiteral(JSAtom* atom, const TokenPos& pos) {
return NodeGeneric;
}
Node newCallSiteObject(uint32_t begin) {
return NodeGeneric;
}
bool addToCallSiteObject(Node callSiteObj, Node rawNode, Node cookedNode) {
return true;
}
Node newThisLiteral(const TokenPos& pos, Node thisName) { return NodeGeneric; }
Node newNullLiteral(const TokenPos& pos) { return NodeGeneric; }
template <class Boxer>
Node newRegExp(RegExpObject* reobj, const TokenPos& pos, Boxer& boxer) { return NodeGeneric; }
Node newConditional(Node cond, Node thenExpr, Node elseExpr) { return NodeGeneric; }
Node newElision() { return NodeGeneric; }
void markAsSetCall(Node node) {
MOZ_ASSERT(node == NodeFunctionCall);
}
Node newDelete(uint32_t begin, Node expr) {
return NodeGeneric;
}
Node newTypeof(uint32_t begin, Node kid) {
return NodeGeneric;
}
Node newUnary(ParseNodeKind kind, JSOp op, uint32_t begin, Node kid) {
return NodeGeneric;
}
Node newBinary(ParseNodeKind kind, JSOp op = JSOP_NOP) { return NodeGeneric; }
Node newBinary(ParseNodeKind kind, Node left, JSOp op = JSOP_NOP) { return NodeGeneric; }
Node newBinary(ParseNodeKind kind, Node left, Node right, JSOp op = JSOP_NOP) {
return NodeGeneric;
}
Node appendOrCreateList(ParseNodeKind kind, Node left, Node right,
ParseContext<SyntaxParseHandler>* pc, JSOp op = JSOP_NOP) {
return NodeGeneric;
}
Node newTernary(ParseNodeKind kind, Node first, Node second, Node third, JSOp op = JSOP_NOP) {
return NodeGeneric;
}
// Expressions
Node newArrayComprehension(Node body, const TokenPos& pos) { return NodeGeneric; }
Node newArrayLiteral(uint32_t begin) { return NodeUnparenthesizedArray; }
bool addElision(Node literal, const TokenPos& pos) { return true; }
bool addSpreadElement(Node literal, uint32_t begin, Node inner) { return true; }
void addArrayElement(Node literal, Node element) { }
Node newCall() { return NodeFunctionCall; }
Node newTaggedTemplate() { return NodeGeneric; }
Node newObjectLiteral(uint32_t begin) { return NodeUnparenthesizedObject; }
Node newClassMethodList(uint32_t begin) { return NodeGeneric; }
Node newNewTarget(Node newHolder, Node targetHolder) { return NodeGeneric; }
Node newPosHolder(const TokenPos& pos) { return NodeGeneric; }
Node newSuperBase(Node thisName, const TokenPos& pos) { return NodeSuperBase; }
bool addPrototypeMutation(Node literal, uint32_t begin, Node expr) { return true; }
bool addPropertyDefinition(Node literal, Node name, Node expr) { return true; }
bool addShorthand(Node literal, Node name, Node expr) { return true; }
bool addObjectMethodDefinition(Node literal, Node name, Node fn, JSOp op) { return true; }
bool addClassMethodDefinition(Node literal, Node name, Node fn, JSOp op, bool isStatic) { return true; }
Node newYieldExpression(uint32_t begin, Node value, Node gen) { return NodeUnparenthesizedYieldExpr; }
Node newYieldStarExpression(uint32_t begin, Node value, Node gen) { return NodeGeneric; }
// Statements
Node newStatementList(unsigned blockid, const TokenPos& pos) { return NodeGeneric; }
void addStatementToList(Node list, Node stmt, ParseContext<SyntaxParseHandler>* pc) {}
bool prependInitialYield(Node stmtList, Node gen) { return true; }
Node newEmptyStatement(const TokenPos& pos) { return NodeEmptyStatement; }
Node newSetThis(Node thisName, Node value) { return value; }
Node newExprStatement(Node expr, uint32_t end) {
return expr == NodeUnparenthesizedString ? NodeStringExprStatement : NodeGeneric;
}
Node newIfStatement(uint32_t begin, Node cond, Node then, Node else_) { return NodeGeneric; }
Node newDoWhileStatement(Node body, Node cond, const TokenPos& pos) { return NodeGeneric; }
Node newWhileStatement(uint32_t begin, Node cond, Node body) { return NodeGeneric; }
Node newSwitchStatement(uint32_t begin, Node discriminant, Node caseList) { return NodeGeneric; }
Node newCaseOrDefault(uint32_t begin, Node expr, Node body) { return NodeGeneric; }
Node newContinueStatement(PropertyName* label, const TokenPos& pos) { return NodeGeneric; }
Node newBreakStatement(PropertyName* label, const TokenPos& pos) { return NodeBreak; }
Node newReturnStatement(Node expr, const TokenPos& pos) { return NodeReturn; }
Node newLabeledStatement(PropertyName* label, Node stmt, uint32_t begin) {
return NodeGeneric;
}
Node newThrowStatement(Node expr, const TokenPos& pos) { return NodeThrow; }
Node newTryStatement(uint32_t begin, Node body, Node catchList, Node finallyBlock) {
return NodeGeneric;
}
Node newDebuggerStatement(const TokenPos& pos) { return NodeGeneric; }
Node newPropertyAccess(Node pn, PropertyName* name, uint32_t end) {
lastAtom = name;
return NodeDottedProperty;
}
Node newPropertyByValue(Node pn, Node kid, uint32_t end) { return NodeElement; }
bool addCatchBlock(Node catchList, Node letBlock,
Node catchName, Node catchGuard, Node catchBody) { return true; }
bool setLastFunctionArgumentDefault(Node funcpn, Node pn) { return true; }
void setLastFunctionArgumentDestructuring(Node funcpn, Node pn) {}
Node newFunctionDefinition() { return NodeHoistableDeclaration; }
void setFunctionBody(Node pn, Node kid) {}
void setFunctionBox(Node pn, FunctionBox* funbox) {}
void addFunctionArgument(Node pn, Node argpn) {}
Node newForStatement(uint32_t begin, Node forHead, Node body, unsigned iflags) {
return NodeGeneric;
}
Node newComprehensionFor(uint32_t begin, Node forHead, Node body) {
return NodeGeneric;
}
Node newForHead(ParseNodeKind kind, Node decls, Node lhs, Node rhs, const TokenPos& pos) {
return NodeGeneric;
}
Node newLexicalScope(ObjectBox* blockbox) { return NodeGeneric; }
void setLexicalScopeBody(Node block, Node body) {}
Node newLetBlock(Node vars, Node block, const TokenPos& pos) {
return NodeGeneric;
}
bool finishInitializerAssignment(Node pn, Node init, JSOp op) { return true; }
void setLexicalDeclarationOp(Node pn, JSOp op) {}
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) {}
void setDerivedClassConstructor(Node pn) {}
void setPosition(Node pn, const TokenPos& pos) {}
TokenPos getPosition(Node pn) {
return tokenStream.currentToken().pos;
}
Node newList(ParseNodeKind kind, JSOp op = JSOP_NOP) {
MOZ_ASSERT(kind != PNK_VAR);
return NodeGeneric;
}
Node newList(ParseNodeKind kind, uint32_t begin, JSOp op = JSOP_NOP) {
return NodeGeneric;
}
Node newDeclarationList(ParseNodeKind kind, JSOp op = JSOP_NOP) {
MOZ_ASSERT(kind == PNK_VAR || kind == PNK_CONST || kind == PNK_LET);
return kind == PNK_VAR ? NodeHoistableDeclaration : NodeGeneric;
}
Node newList(ParseNodeKind kind, Node kid, JSOp op = JSOP_NOP) {
MOZ_ASSERT(kind != PNK_VAR);
return NodeGeneric;
}
Node newDeclarationList(ParseNodeKind kind, Node kid, JSOp op = JSOP_NOP) {
MOZ_ASSERT(kind == PNK_VAR || kind == PNK_CONST || kind == PNK_LET);
return kind == PNK_VAR ? NodeHoistableDeclaration : NodeGeneric;
}
Node newCatchList() {
return newList(PNK_CATCHLIST, JSOP_NOP);
}
Node newCommaExpressionList(Node kid) {
return NodeUnparenthesizedCommaExpr;
}
void addList(Node list, Node kid) {
MOZ_ASSERT(list == NodeGeneric ||
list == NodeUnparenthesizedArray ||
list == NodeUnparenthesizedObject ||
list == NodeUnparenthesizedCommaExpr ||
list == NodeHoistableDeclaration ||
list == NodeFunctionCall);
}
Node newAssignment(ParseNodeKind kind, Node lhs, Node rhs,
ParseContext<SyntaxParseHandler>* pc, JSOp op)
{
if (kind == PNK_ASSIGN)
return NodeUnparenthesizedAssignment;
return newBinary(kind, lhs, rhs, op);
}
bool isUnparenthesizedYieldExpression(Node node) {
return node == NodeUnparenthesizedYieldExpr;
}
bool isUnparenthesizedCommaExpression(Node node) {
return node == NodeUnparenthesizedCommaExpr;
}
bool isUnparenthesizedAssignment(Node node) {
return node == NodeUnparenthesizedAssignment;
}
bool isReturnStatement(Node node) {
return node == NodeReturn;
}
bool isStatementPermittedAfterReturnStatement(Node pn) {
return pn == NodeHoistableDeclaration || pn == NodeBreak || pn == NodeThrow ||
pn == NodeEmptyStatement;
}
bool isSuperBase(Node pn) {
return pn == NodeSuperBase;
}
void setOp(Node pn, JSOp op) {}
void setBlockId(Node pn, unsigned blockid) {}
void setFlag(Node pn, unsigned flag) {}
void setListFlag(Node pn, unsigned flag) {}
MOZ_WARN_UNUSED_RESULT 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 == NodeUnparenthesizedArgumentsName)
return NodeParenthesizedArgumentsName;
if (node == NodeUnparenthesizedEvalName)
return NodeParenthesizedEvalName;
if (node == NodeUnparenthesizedName)
return NodeParenthesizedName;
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 == NodeUnparenthesizedCommaExpr ||
node == NodeUnparenthesizedYieldExpr ||
node == NodeUnparenthesizedAssignment)
{
return NodeGeneric;
}
// In all other cases, the parenthesized form of |node| is equivalent
// to the unparenthesized form: return |node| unchanged.
return node;
}
MOZ_WARN_UNUSED_RESULT Node setLikelyIIFE(Node pn) {
return pn; // Remain in syntax-parse mode.
}
void setPrologue(Node pn) {}
bool isConstant(Node pn) { return false; }
PropertyName* maybeUnparenthesizedName(Node node) {
if (node == NodeUnparenthesizedName ||
node == NodeUnparenthesizedArgumentsName ||
node == NodeUnparenthesizedEvalName)
{
return lastAtom->asPropertyName();
}
return nullptr;
}
PropertyName* maybeParenthesizedName(Node node) {
if (node == NodeParenthesizedName ||
node == NodeParenthesizedArgumentsName ||
node == NodeParenthesizedEvalName)
{
return lastAtom->asPropertyName();
}
return nullptr;
}
PropertyName* maybeNameAnyParentheses(Node node) {
if (PropertyName* name = maybeUnparenthesizedName(node))
return name;
return maybeParenthesizedName(node);
}
PropertyName* 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)
return nullptr;
return lastAtom->asPropertyName();
}
JSAtom* isStringExprStatement(Node pn, TokenPos* pos) {
if (pn == NodeStringExprStatement) {
*pos = lastStringPos;
return lastAtom;
}
return nullptr;
}
void markAsAssigned(Node node) {}
void adjustGetToSet(Node node) {}
void maybeDespecializeSet(Node node) {}
Node makeAssignment(Node pn, Node rhs) { return NodeGeneric; }
static Node getDefinitionNode(DefinitionNode dn) { return NodeGeneric; }
static Definition::Kind getDefinitionKind(DefinitionNode dn) { return dn; }
static bool isPlaceholderDefinition(DefinitionNode dn) { return dn == Definition::PLACEHOLDER; }
void linkUseToDef(Node pn, DefinitionNode dn) {}
DefinitionNode resolve(DefinitionNode dn) { return dn; }
void deoptimizeUsesWithin(DefinitionNode dn, const TokenPos& pos) {}
bool dependencyCovered(Node pn, unsigned blockid, bool functionScope) {
// Only resolve lexical dependencies in cases where a definition covers
// the entire function. Not enough information is kept to compare the
// dependency location with blockid.
return functionScope;
}
void markMaybeUninitializedLexicalUseInSwitch(Node pn, DefinitionNode dn,
uint16_t firstDominatingLexicalSlot) {}
static uintptr_t definitionToBits(DefinitionNode dn) {
// Use a shift, as DefinitionList tags the lower bit of its associated union.
return uintptr_t(dn << 1);
}
static DefinitionNode definitionFromBits(uintptr_t bits) {
return (DefinitionNode) (bits >> 1);
}
static DefinitionNode nullDefinition() {
return Definition::MISSING;
}
void disableSyntaxParser() {
}
};
} // namespace frontend
} // namespace js
#endif /* frontend_SyntaxParseHandler_h */