Mypal/js/src/frontend/Parser.cpp

9564 lines
315 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sts=4 et sw=4 tw=99:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*
* JS parser.
*
* This is a recursive-descent parser for the JavaScript language specified by
* "The ECMAScript Language Specification" (Standard ECMA-262). It uses
* lexical and semantic feedback to disambiguate non-LL(1) structures. It
* generates trees of nodes induced by the recursive parsing (not precise
* syntax trees, see Parser.h). After tree construction, it rewrites trees to
* fold constants and evaluate compile-time expressions.
*
* This parser attempts no error recovery.
*/
#include "frontend/Parser.h"
#include <new>
#include "jsapi.h"
#include "jsatom.h"
#include "jscntxt.h"
#include "jsfun.h"
#include "jsopcode.h"
#include "jsscript.h"
#include "jstypes.h"
#include "builtin/ModuleObject.h"
#include "builtin/SelfHostingDefines.h"
#include "frontend/BytecodeCompiler.h"
#include "frontend/FoldConstants.h"
#include "frontend/TokenStream.h"
#include "wasm/AsmJS.h"
#include "jsatominlines.h"
#include "jsscriptinlines.h"
#include "frontend/ParseNode-inl.h"
#include "vm/EnvironmentObject-inl.h"
using namespace js;
using namespace js::gc;
using mozilla::Maybe;
using mozilla::Move;
using mozilla::Nothing;
using mozilla::PodCopy;
using mozilla::PodZero;
using mozilla::Some;
using JS::AutoGCRooter;
namespace js {
namespace frontend {
using DeclaredNamePtr = ParseContext::Scope::DeclaredNamePtr;
using AddDeclaredNamePtr = ParseContext::Scope::AddDeclaredNamePtr;
using BindingIter = ParseContext::Scope::BindingIter;
using UsedNamePtr = UsedNameTracker::UsedNameMap::Ptr;
/* Read a token. Report an error and return null() if that token isn't of type tt. */
#define MUST_MATCH_TOKEN_MOD(tt, modifier, errorNumber) \
JS_BEGIN_MACRO \
TokenKind token; \
if (!tokenStream.getToken(&token, modifier)) \
return null(); \
if (token != tt) { \
error(errorNumber); \
return null(); \
} \
JS_END_MACRO
#define MUST_MATCH_TOKEN(tt, errno) MUST_MATCH_TOKEN_MOD(tt, TokenStream::None, errno)
template <class T, class U>
static inline void
PropagateTransitiveParseFlags(const T* inner, U* outer)
{
if (inner->bindingsAccessedDynamically())
outer->setBindingsAccessedDynamically();
if (inner->hasDebuggerStatement())
outer->setHasDebuggerStatement();
if (inner->hasDirectEval())
outer->setHasDirectEval();
}
static const char*
DeclarationKindString(DeclarationKind kind)
{
switch (kind) {
case DeclarationKind::PositionalFormalParameter:
case DeclarationKind::FormalParameter:
return "formal parameter";
case DeclarationKind::CoverArrowParameter:
return "cover arrow parameter";
case DeclarationKind::Var:
return "var";
case DeclarationKind::Let:
return "let";
case DeclarationKind::Const:
return "const";
case DeclarationKind::Import:
return "import";
case DeclarationKind::BodyLevelFunction:
case DeclarationKind::LexicalFunction:
return "function";
case DeclarationKind::VarForAnnexBLexicalFunction:
return "annex b var";
case DeclarationKind::ForOfVar:
return "var in for-of";
case DeclarationKind::SimpleCatchParameter:
case DeclarationKind::CatchParameter:
return "catch parameter";
}
MOZ_CRASH("Bad DeclarationKind");
}
static bool
StatementKindIsBraced(StatementKind kind)
{
return kind == StatementKind::Block ||
kind == StatementKind::Switch ||
kind == StatementKind::Try ||
kind == StatementKind::Catch ||
kind == StatementKind::Finally;
}
void
ParseContext::Scope::dump(ParseContext* pc)
{
ExclusiveContext* cx = pc->sc()->context;
fprintf(stdout, "ParseScope %p", this);
fprintf(stdout, "\n decls:\n");
for (DeclaredNameMap::Range r = declared_->all(); !r.empty(); r.popFront()) {
JSAutoByteString bytes;
if (!AtomToPrintableString(cx, r.front().key(), &bytes))
return;
DeclaredNameInfo& info = r.front().value().wrapped;
fprintf(stdout, " %s %s%s\n",
DeclarationKindString(info.kind()),
bytes.ptr(),
info.closedOver() ? " (closed over)" : "");
}
fprintf(stdout, "\n");
}
/* static */ void
ParseContext::Scope::removeVarForAnnexBLexicalFunction(ParseContext* pc, JSAtom* name)
{
// Local strict mode is allowed, e.g., a class binding removing a
// synthesized Annex B binding.
MOZ_ASSERT(!pc->sc()->strictScript);
for (ParseContext::Scope* scope = pc->innermostScope();
scope != pc->varScope().enclosing();
scope = scope->enclosing())
{
if (DeclaredNamePtr p = scope->declared_->lookup(name)) {
if (p->value()->kind() == DeclarationKind::VarForAnnexBLexicalFunction)
scope->declared_->remove(p);
}
}
// Annex B semantics no longer applies to any functions with this name, as
// an early error would have occurred.
pc->removeInnerFunctionBoxesForAnnexB(name);
}
static bool
DeclarationKindIsCatchParameter(DeclarationKind kind)
{
return kind == DeclarationKind::SimpleCatchParameter ||
kind == DeclarationKind::CatchParameter;
}
bool
ParseContext::Scope::addCatchParameters(ParseContext* pc, Scope& catchParamScope)
{
if (pc->useAsmOrInsideUseAsm())
return true;
for (DeclaredNameMap::Range r = catchParamScope.declared_->all(); !r.empty(); r.popFront()) {
DeclarationKind kind = r.front().value()->kind();
MOZ_ASSERT(DeclarationKindIsCatchParameter(kind));
JSAtom* name = r.front().key();
AddDeclaredNamePtr p = lookupDeclaredNameForAdd(name);
MOZ_ASSERT(!p);
if (!addDeclaredName(pc, p, name, kind))
return false;
}
return true;
}
void
ParseContext::Scope::removeCatchParameters(ParseContext* pc, Scope& catchParamScope)
{
if (pc->useAsmOrInsideUseAsm())
return;
for (DeclaredNameMap::Range r = catchParamScope.declared_->all(); !r.empty(); r.popFront()) {
DeclaredNamePtr p = declared_->lookup(r.front().key());
MOZ_ASSERT(p);
// This check is needed because the catch body could have declared
// vars, which would have been added to catchParamScope.
if (DeclarationKindIsCatchParameter(r.front().value()->kind()))
declared_->remove(p);
}
}
void
SharedContext::computeAllowSyntax(Scope* scope)
{
for (ScopeIter si(scope); si; si++) {
if (si.kind() == ScopeKind::Function) {
JSFunction* fun = si.scope()->as<FunctionScope>().canonicalFunction();
if (fun->isArrow())
continue;
allowNewTarget_ = true;
allowSuperProperty_ = fun->allowSuperProperty();
allowSuperCall_ = fun->isDerivedClassConstructor();
return;
}
}
}
void
SharedContext::computeThisBinding(Scope* scope)
{
for (ScopeIter si(scope); si; si++) {
if (si.kind() == ScopeKind::Module) {
thisBinding_ = ThisBinding::Module;
return;
}
if (si.kind() == ScopeKind::Function) {
JSFunction* fun = si.scope()->as<FunctionScope>().canonicalFunction();
// Arrow functions and generator expression lambdas don't have
// their own `this` binding.
if (fun->isArrow() || fun->nonLazyScript()->isGeneratorExp())
continue;
// Derived class constructors (including nested arrow functions and
// eval) need TDZ checks when accessing |this|.
if (fun->isDerivedClassConstructor())
needsThisTDZChecks_ = true;
thisBinding_ = ThisBinding::Function;
return;
}
}
thisBinding_ = ThisBinding::Global;
}
void
SharedContext::computeInWith(Scope* scope)
{
for (ScopeIter si(scope); si; si++) {
if (si.kind() == ScopeKind::With) {
inWith_ = true;
break;
}
}
}
EvalSharedContext::EvalSharedContext(ExclusiveContext* cx, JSObject* enclosingEnv,
Scope* enclosingScope, Directives directives,
bool extraWarnings)
: SharedContext(cx, Kind::Eval, directives, extraWarnings),
enclosingScope_(cx, enclosingScope),
bindings(cx)
{
computeAllowSyntax(enclosingScope);
computeInWith(enclosingScope);
computeThisBinding(enclosingScope);
// Like all things Debugger, Debugger.Frame.eval needs special
// handling. Since the environment chain of such evals are non-syntactic
// (DebuggerEnvironmentProxy is not an EnvironmentObject), computing the
// this binding with respect to enclosingScope is incorrect if the
// Debugger.Frame is a function frame. Recompute the this binding if we
// are such an eval.
if (enclosingEnv && enclosingScope->hasOnChain(ScopeKind::NonSyntactic)) {
// For Debugger.Frame.eval with bindings, the environment chain may
// have more than the DebugEnvironmentProxy.
JSObject* env = enclosingEnv;
while (env) {
if (env->is<DebugEnvironmentProxy>())
env = &env->as<DebugEnvironmentProxy>().environment();
if (env->is<CallObject>()) {
computeThisBinding(env->as<CallObject>().callee().nonLazyScript()->bodyScope());
break;
}
env = env->enclosingEnvironment();
}
}
}
bool
ParseContext::init()
{
if (scriptId_ == UINT32_MAX) {
tokenStream_.reportError(JSMSG_NEED_DIET, js_script_str);
return false;
}
ExclusiveContext* cx = sc()->context;
if (isFunctionBox()) {
// Named lambdas always need a binding for their own name. If this
// binding is closed over when we finish parsing the function in
// finishExtraFunctionScopes, the function box needs to be marked as
// needing a dynamic DeclEnv object.
RootedFunction fun(cx, functionBox()->function());
if (fun->isNamedLambda()) {
if (!namedLambdaScope_->init(this))
return false;
AddDeclaredNamePtr p =
namedLambdaScope_->lookupDeclaredNameForAdd(fun->explicitName());
MOZ_ASSERT(!p);
if (!namedLambdaScope_->addDeclaredName(this, p, fun->explicitName(),
DeclarationKind::Const))
{
return false;
}
}
if (!functionScope_->init(this))
return false;
if (!positionalFormalParameterNames_.acquire(cx))
return false;
}
if (!closedOverBindingsForLazy_.acquire(cx))
return false;
if (!sc()->strict()) {
if (!innerFunctionBoxesForAnnexB_.acquire(cx))
return false;
}
return true;
}
bool
ParseContext::addInnerFunctionBoxForAnnexB(FunctionBox* funbox)
{
for (uint32_t i = 0; i < innerFunctionBoxesForAnnexB_->length(); i++) {
if (!innerFunctionBoxesForAnnexB_[i]) {
innerFunctionBoxesForAnnexB_[i] = funbox;
return true;
}
}
return innerFunctionBoxesForAnnexB_->append(funbox);
}
void
ParseContext::removeInnerFunctionBoxesForAnnexB(JSAtom* name)
{
for (uint32_t i = 0; i < innerFunctionBoxesForAnnexB_->length(); i++) {
if (FunctionBox* funbox = innerFunctionBoxesForAnnexB_[i]) {
if (funbox->function()->explicitName() == name)
innerFunctionBoxesForAnnexB_[i] = nullptr;
}
}
}
void
ParseContext::finishInnerFunctionBoxesForAnnexB()
{
// Strict mode doesn't have wack Annex B function semantics. Or we
// could've failed to initialize ParseContext.
if (sc()->strict() || !innerFunctionBoxesForAnnexB_)
return;
for (uint32_t i = 0; i < innerFunctionBoxesForAnnexB_->length(); i++) {
if (FunctionBox* funbox = innerFunctionBoxesForAnnexB_[i])
funbox->isAnnexB = true;
}
}
ParseContext::~ParseContext()
{
// Any funboxes still in the list at the end of parsing means no early
// error would have occurred for declaring a binding in the nearest var
// scope. Mark them as needing extra assignments to this var binding.
finishInnerFunctionBoxesForAnnexB();
}
bool
UsedNameTracker::noteUse(ExclusiveContext* cx, JSAtom* name, uint32_t scriptId, uint32_t scopeId)
{
if (UsedNameMap::AddPtr p = map_.lookupForAdd(name)) {
if (!p->value().noteUsedInScope(scriptId, scopeId))
return false;
} else {
UsedNameInfo info(cx);
if (!info.noteUsedInScope(scriptId, scopeId))
return false;
if (!map_.add(p, name, Move(info)))
return false;
}
return true;
}
void
UsedNameTracker::UsedNameInfo::resetToScope(uint32_t scriptId, uint32_t scopeId)
{
while (!uses_.empty()) {
Use& innermost = uses_.back();
if (innermost.scopeId < scopeId)
break;
MOZ_ASSERT(innermost.scriptId >= scriptId);
uses_.popBack();
}
}
void
UsedNameTracker::rewind(RewindToken token)
{
scriptCounter_ = token.scriptId;
scopeCounter_ = token.scopeId;
for (UsedNameMap::Range r = map_.all(); !r.empty(); r.popFront())
r.front().value().resetToScope(token.scriptId, token.scopeId);
}
FunctionBox::FunctionBox(ExclusiveContext* cx, LifoAlloc& alloc, ObjectBox* traceListHead,
JSFunction* fun, uint32_t preludeStart,
Directives directives, bool extraWarnings,
GeneratorKind generatorKind, FunctionAsyncKind asyncKind)
: ObjectBox(fun, traceListHead),
SharedContext(cx, Kind::ObjectBox, directives, extraWarnings),
enclosingScope_(nullptr),
namedLambdaBindings_(nullptr),
functionScopeBindings_(nullptr),
extraVarScopeBindings_(nullptr),
functionNode(nullptr),
bufStart(0),
bufEnd(0),
startLine(1),
startColumn(0),
preludeStart(preludeStart),
length(0),
generatorKindBits_(GeneratorKindAsBits(generatorKind)),
asyncKindBits_(AsyncKindAsBits(asyncKind)),
isGenexpLambda(false),
hasDestructuringArgs(false),
hasParameterExprs(false),
hasDirectEvalInParameterExpr(false),
hasDuplicateParameters(false),
useAsm(false),
insideUseAsm(false),
isAnnexB(false),
wasEmitted(false),
declaredArguments(false),
usesArguments(false),
usesApply(false),
usesThis(false),
usesReturn(false),
hasRest_(false),
funCxFlags()
{
// Functions created at parse time may be set singleton after parsing and
// baked into JIT code, so they must be allocated tenured. They are held by
// the JSScript so cannot be collected during a minor GC anyway.
MOZ_ASSERT(fun->isTenured());
}
void
FunctionBox::initFromLazyFunction()
{
JSFunction* fun = function();
if (fun->lazyScript()->isDerivedClassConstructor())
setDerivedClassConstructor();
if (fun->lazyScript()->needsHomeObject())
setNeedsHomeObject();
enclosingScope_ = fun->lazyScript()->enclosingScope();
initWithEnclosingScope(enclosingScope_);
}
void
FunctionBox::initStandaloneFunction(Scope* enclosingScope)
{
// Standalone functions are Function or Generator constructors and are
// always scoped to the global.
MOZ_ASSERT(enclosingScope->is<GlobalScope>());
enclosingScope_ = enclosingScope;
allowNewTarget_ = true;
thisBinding_ = ThisBinding::Function;
}
void
FunctionBox::initWithEnclosingParseContext(ParseContext* enclosing, FunctionSyntaxKind kind)
{
SharedContext* sc = enclosing->sc();
useAsm = sc->isFunctionBox() && sc->asFunctionBox()->useAsmOrInsideUseAsm();
JSFunction* fun = function();
// Arrow functions and generator expression lambdas don't have
// their own `this` binding.
if (fun->isArrow()) {
allowNewTarget_ = sc->allowNewTarget();
allowSuperProperty_ = sc->allowSuperProperty();
allowSuperCall_ = sc->allowSuperCall();
needsThisTDZChecks_ = sc->needsThisTDZChecks();
thisBinding_ = sc->thisBinding();
} else {
allowNewTarget_ = true;
allowSuperProperty_ = fun->allowSuperProperty();
if (kind == DerivedClassConstructor) {
setDerivedClassConstructor();
allowSuperCall_ = true;
needsThisTDZChecks_ = true;
}
if (isGenexpLambda)
thisBinding_ = sc->thisBinding();
else
thisBinding_ = ThisBinding::Function;
}
if (sc->inWith()) {
inWith_ = true;
} else {
auto isWith = [](ParseContext::Statement* stmt) {
return stmt->kind() == StatementKind::With;
};
inWith_ = enclosing->findInnermostStatement(isWith);
}
}
void
FunctionBox::initWithEnclosingScope(Scope* enclosingScope)
{
if (!function()->isArrow()) {
allowNewTarget_ = true;
allowSuperProperty_ = function()->allowSuperProperty();
if (isDerivedClassConstructor()) {
setDerivedClassConstructor();
allowSuperCall_ = true;
needsThisTDZChecks_ = true;
}
thisBinding_ = ThisBinding::Function;
} else {
computeAllowSyntax(enclosingScope);
computeThisBinding(enclosingScope);
}
computeInWith(enclosingScope);
}
template <typename ParseHandler>
void
Parser<ParseHandler>::error(unsigned errorNumber, ...)
{
va_list args;
va_start(args, errorNumber);
#ifdef DEBUG
bool result =
#endif
tokenStream.reportCompileErrorNumberVA(pos().begin, JSREPORT_ERROR, errorNumber, args);
MOZ_ASSERT(!result, "reporting an error returned true?");
va_end(args);
}
template <typename ParseHandler>
void
Parser<ParseHandler>::errorAt(uint32_t offset, unsigned errorNumber, ...)
{
va_list args;
va_start(args, errorNumber);
#ifdef DEBUG
bool result =
#endif
tokenStream.reportCompileErrorNumberVA(offset, JSREPORT_ERROR, errorNumber, args);
MOZ_ASSERT(!result, "reporting an error returned true?");
va_end(args);
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::warning(unsigned errorNumber, ...)
{
va_list args;
va_start(args, errorNumber);
bool result =
tokenStream.reportCompileErrorNumberVA(pos().begin, JSREPORT_WARNING, errorNumber, args);
va_end(args);
return result;
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::warningAt(uint32_t offset, unsigned errorNumber, ...)
{
va_list args;
va_start(args, errorNumber);
bool result =
tokenStream.reportCompileErrorNumberVA(offset, JSREPORT_WARNING, errorNumber, args);
va_end(args);
return result;
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::extraWarning(unsigned errorNumber, ...)
{
va_list args;
va_start(args, errorNumber);
bool result = tokenStream.reportExtraWarningErrorNumberVA(pos().begin, errorNumber, args);
va_end(args);
return result;
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::strictModeError(unsigned errorNumber, ...)
{
va_list args;
va_start(args, errorNumber);
bool res =
tokenStream.reportStrictModeErrorNumberVA(pos().begin, pc->sc()->strict(),
errorNumber, args);
va_end(args);
return res;
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::strictModeErrorAt(uint32_t offset, unsigned errorNumber, ...)
{
va_list args;
va_start(args, errorNumber);
bool res =
tokenStream.reportStrictModeErrorNumberVA(offset, pc->sc()->strict(), errorNumber, args);
va_end(args);
return res;
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::reportNoOffset(ParseReportKind kind, bool strict, unsigned errorNumber, ...)
{
va_list args;
va_start(args, errorNumber);
bool result = false;
uint32_t offset = TokenStream::NoOffset;
switch (kind) {
case ParseError:
result = tokenStream.reportCompileErrorNumberVA(offset, JSREPORT_ERROR, errorNumber, args);
break;
case ParseWarning:
result =
tokenStream.reportCompileErrorNumberVA(offset, JSREPORT_WARNING, errorNumber, args);
break;
case ParseExtraWarning:
result = tokenStream.reportExtraWarningErrorNumberVA(offset, errorNumber, args);
break;
case ParseStrictError:
result = tokenStream.reportStrictModeErrorNumberVA(offset, strict, errorNumber, args);
break;
}
va_end(args);
return result;
}
template <>
bool
Parser<FullParseHandler>::abortIfSyntaxParser()
{
handler.disableSyntaxParser();
return true;
}
template <>
bool
Parser<SyntaxParseHandler>::abortIfSyntaxParser()
{
abortedSyntaxParse = true;
return false;
}
template <typename ParseHandler>
Parser<ParseHandler>::Parser(ExclusiveContext* cx, LifoAlloc& alloc,
const ReadOnlyCompileOptions& options,
const char16_t* chars, size_t length,
bool foldConstants,
UsedNameTracker& usedNames,
Parser<SyntaxParseHandler>* syntaxParser,
LazyScript* lazyOuterFunction)
: AutoGCRooter(cx, PARSER),
context(cx),
alloc(alloc),
tokenStream(cx, options, chars, length, thisForCtor()),
traceListHead(nullptr),
pc(nullptr),
usedNames(usedNames),
sct(nullptr),
ss(nullptr),
keepAtoms(cx->perThreadData),
foldConstants(foldConstants),
#ifdef DEBUG
checkOptionsCalled(false),
#endif
abortedSyntaxParse(false),
isUnexpectedEOF_(false),
handler(cx, alloc, tokenStream, syntaxParser, lazyOuterFunction)
{
cx->perThreadData->frontendCollectionPool.addActiveCompilation();
// The Mozilla specific JSOPTION_EXTRA_WARNINGS option adds extra warnings
// which are not generated if functions are parsed lazily. Note that the
// standard "use strict" does not inhibit lazy parsing.
if (options.extraWarningsOption)
handler.disableSyntaxParser();
tempPoolMark = alloc.mark();
}
template<typename ParseHandler>
bool
Parser<ParseHandler>::checkOptions()
{
#ifdef DEBUG
checkOptionsCalled = true;
#endif
if (!tokenStream.checkOptions())
return false;
return true;
}
template <typename ParseHandler>
Parser<ParseHandler>::~Parser()
{
MOZ_ASSERT(checkOptionsCalled);
alloc.release(tempPoolMark);
/*
* The parser can allocate enormous amounts of memory for large functions.
* Eagerly free the memory now (which otherwise won't be freed until the
* next GC) to avoid unnecessary OOMs.
*/
alloc.freeAllIfHugeAndUnused();
context->perThreadData->frontendCollectionPool.removeActiveCompilation();
}
template <typename ParseHandler>
ObjectBox*
Parser<ParseHandler>::newObjectBox(JSObject* obj)
{
MOZ_ASSERT(obj);
/*
* We use JSContext.tempLifoAlloc to allocate parsed objects and place them
* on a list in this Parser to ensure GC safety. Thus the tempLifoAlloc
* arenas containing the entries must be alive until we are done with
* scanning, parsing and code generation for the whole script or top-level
* function.
*/
ObjectBox* objbox = alloc.new_<ObjectBox>(obj, traceListHead);
if (!objbox) {
ReportOutOfMemory(context);
return nullptr;
}
traceListHead = objbox;
return objbox;
}
template <typename ParseHandler>
FunctionBox*
Parser<ParseHandler>::newFunctionBox(Node fn, JSFunction* fun, uint32_t preludeStart,
Directives inheritedDirectives,
GeneratorKind generatorKind, FunctionAsyncKind asyncKind,
bool tryAnnexB)
{
MOZ_ASSERT(fun);
MOZ_ASSERT_IF(tryAnnexB, !pc->sc()->strict());
/*
* We use JSContext.tempLifoAlloc to allocate parsed objects and place them
* on a list in this Parser to ensure GC safety. Thus the tempLifoAlloc
* arenas containing the entries must be alive until we are done with
* scanning, parsing and code generation for the whole script or top-level
* function.
*/
FunctionBox* funbox =
alloc.new_<FunctionBox>(context, alloc, traceListHead, fun, preludeStart,
inheritedDirectives, options().extraWarningsOption,
generatorKind, asyncKind);
if (!funbox) {
ReportOutOfMemory(context);
return nullptr;
}
traceListHead = funbox;
if (fn)
handler.setFunctionBox(fn, funbox);
if (tryAnnexB && !pc->addInnerFunctionBoxForAnnexB(funbox))
return nullptr;
return funbox;
}
ModuleSharedContext::ModuleSharedContext(ExclusiveContext* cx, ModuleObject* module,
Scope* enclosingScope, ModuleBuilder& builder)
: SharedContext(cx, Kind::Module, Directives(true), false),
module_(cx, module),
enclosingScope_(cx, enclosingScope),
bindings(cx),
builder(builder)
{
thisBinding_ = ThisBinding::Module;
}
template <typename ParseHandler>
void
Parser<ParseHandler>::trace(JSTracer* trc)
{
ObjectBox::TraceList(trc, traceListHead);
}
void
MarkParser(JSTracer* trc, AutoGCRooter* parser)
{
static_cast<Parser<FullParseHandler>*>(parser)->trace(trc);
}
/*
* Parse a top-level JS script.
*/
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::parse()
{
MOZ_ASSERT(checkOptionsCalled);
Directives directives(options().strictOption);
GlobalSharedContext globalsc(context, ScopeKind::Global,
directives, options().extraWarningsOption);
ParseContext globalpc(this, &globalsc, /* newDirectives = */ nullptr);
if (!globalpc.init())
return null();
ParseContext::VarScope varScope(this);
if (!varScope.init(pc))
return null();
Node pn = statementList(YieldIsName);
if (!pn)
return null();
TokenKind tt;
if (!tokenStream.getToken(&tt, TokenStream::Operand))
return null();
if (tt != TOK_EOF) {
error(JSMSG_GARBAGE_AFTER_INPUT, "script", TokenKindToDesc(tt));
return null();
}
if (foldConstants) {
if (!FoldConstants(context, &pn, this))
return null();
}
return pn;
}
/*
* Strict mode forbids introducing new definitions for 'eval', 'arguments', or
* for any strict mode reserved keyword.
*/
template <typename ParseHandler>
bool
Parser<ParseHandler>::isValidStrictBinding(PropertyName* name)
{
return name != context->names().eval &&
name != context->names().arguments &&
name != context->names().let &&
name != context->names().static_ &&
!(IsKeyword(name) && name != context->names().await);
}
/*
* Check that it is permitted to introduce a binding for |name|. Use |pos| for
* reporting error locations.
*/
template <typename ParseHandler>
bool
Parser<ParseHandler>::checkStrictBinding(PropertyName* name, TokenPos pos)
{
if (!pc->sc()->needStrictChecks())
return true;
if (!isValidStrictBinding(name)) {
JSAutoByteString bytes;
if (!AtomToPrintableString(context, name, &bytes))
return false;
return strictModeErrorAt(pos.begin, JSMSG_BAD_BINDING, bytes.ptr());
}
return true;
}
/*
* Returns true if all parameter names are valid strict mode binding names and
* no duplicate parameter names are present.
*/
template <typename ParseHandler>
bool
Parser<ParseHandler>::hasValidSimpleStrictParameterNames()
{
MOZ_ASSERT(pc->isFunctionBox() && pc->functionBox()->hasSimpleParameterList());
if (pc->functionBox()->hasDuplicateParameters)
return false;
for (size_t i = 0; i < pc->positionalFormalParameterNames().length(); i++) {
JSAtom* name = pc->positionalFormalParameterNames()[i];
MOZ_ASSERT(name);
if (!isValidStrictBinding(name->asPropertyName()))
return false;
}
return true;
}
template <typename ParseHandler>
void
Parser<ParseHandler>::reportRedeclaration(HandlePropertyName name, DeclarationKind kind,
TokenPos pos)
{
JSAutoByteString bytes;
if (!AtomToPrintableString(context, name, &bytes))
return;
errorAt(pos.begin, JSMSG_REDECLARED_VAR, DeclarationKindString(kind), bytes.ptr());
}
// notePositionalFormalParameter is called for both the arguments of a regular
// function definition and the arguments specified by the Function
// constructor.
//
// The 'disallowDuplicateParams' bool indicates whether the use of another
// feature (destructuring or default arguments) disables duplicate arguments.
// (ECMA-262 requires us to support duplicate parameter names, but, for newer
// features, we consider the code to have "opted in" to higher standards and
// forbid duplicates.)
template <typename ParseHandler>
bool
Parser<ParseHandler>::notePositionalFormalParameter(Node fn, HandlePropertyName name,
bool disallowDuplicateParams,
bool* duplicatedParam)
{
if (AddDeclaredNamePtr p = pc->functionScope().lookupDeclaredNameForAdd(name)) {
if (disallowDuplicateParams) {
error(JSMSG_BAD_DUP_ARGS);
return false;
}
// Strict-mode disallows duplicate args. We may not know whether we are
// in strict mode or not (since the function body hasn't been parsed).
// In such cases, report will queue up the potential error and return
// 'true'.
if (pc->sc()->needStrictChecks()) {
JSAutoByteString bytes;
if (!AtomToPrintableString(context, name, &bytes))
return false;
if (!strictModeError(JSMSG_DUPLICATE_FORMAL, bytes.ptr()))
return false;
}
*duplicatedParam = true;
} else {
DeclarationKind kind = DeclarationKind::PositionalFormalParameter;
if (!pc->functionScope().addDeclaredName(pc, p, name, kind))
return false;
}
if (!pc->positionalFormalParameterNames().append(name)) {
ReportOutOfMemory(context);
return false;
}
Node paramNode = newName(name);
if (!paramNode)
return false;
if (!checkStrictBinding(name, pos()))
return false;
handler.addFunctionFormalParameter(fn, paramNode);
return true;
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::noteDestructuredPositionalFormalParameter(Node fn, Node destruct)
{
// Append an empty name to the positional formals vector to keep track of
// argument slots when making FunctionScope::Data.
if (!pc->positionalFormalParameterNames().append(nullptr)) {
ReportOutOfMemory(context);
return false;
}
handler.addFunctionFormalParameter(fn, destruct);
return true;
}
static bool
DeclarationKindIsVar(DeclarationKind kind)
{
return kind == DeclarationKind::Var ||
kind == DeclarationKind::BodyLevelFunction ||
kind == DeclarationKind::VarForAnnexBLexicalFunction ||
kind == DeclarationKind::ForOfVar;
}
template <typename ParseHandler>
Maybe<DeclarationKind>
Parser<ParseHandler>::isVarRedeclaredInEval(HandlePropertyName name, DeclarationKind kind)
{
MOZ_ASSERT(DeclarationKindIsVar(kind));
MOZ_ASSERT(pc->sc()->isEvalContext());
// In the case of eval, we also need to check enclosing VM scopes to see
// if the var declaration is allowed in the context.
//
// This check is necessary in addition to
// js::CheckEvalDeclarationConflicts because we only know during parsing
// if a var is bound by for-of.
Scope* enclosingScope = pc->sc()->compilationEnclosingScope();
Scope* varScope = EvalScope::nearestVarScopeForDirectEval(enclosingScope);
MOZ_ASSERT(varScope);
for (ScopeIter si(enclosingScope); si; si++) {
for (js::BindingIter bi(si.scope()); bi; bi++) {
if (bi.name() != name)
continue;
switch (bi.kind()) {
case BindingKind::Let: {
// Annex B.3.5 allows redeclaring simple (non-destructured)
// catch parameters with var declarations, except when it
// appears in a for-of.
bool annexB35Allowance = si.kind() == ScopeKind::SimpleCatch &&
kind != DeclarationKind::ForOfVar;
if (!annexB35Allowance) {
return Some(ScopeKindIsCatch(si.kind())
? DeclarationKind::CatchParameter
: DeclarationKind::Let);
}
break;
}
case BindingKind::Const:
return Some(DeclarationKind::Const);
case BindingKind::Import:
case BindingKind::FormalParameter:
case BindingKind::Var:
case BindingKind::NamedLambdaCallee:
break;
}
}
if (si.scope() == varScope)
break;
}
return Nothing();
}
static bool
DeclarationKindIsParameter(DeclarationKind kind)
{
return kind == DeclarationKind::PositionalFormalParameter ||
kind == DeclarationKind::FormalParameter;
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::tryDeclareVar(HandlePropertyName name, DeclarationKind kind,
Maybe<DeclarationKind>* redeclaredKind)
{
MOZ_ASSERT(DeclarationKindIsVar(kind));
// It is an early error if a 'var' declaration appears inside a
// scope contour that has a lexical declaration of the same name. For
// example, the following are early errors:
//
// { let x; var x; }
// { { var x; } let x; }
//
// And the following are not:
//
// { var x; var x; }
// { { let x; } var x; }
for (ParseContext::Scope* scope = pc->innermostScope();
scope != pc->varScope().enclosing();
scope = scope->enclosing())
{
if (AddDeclaredNamePtr p = scope->lookupDeclaredNameForAdd(name)) {
DeclarationKind declaredKind = p->value()->kind();
if (DeclarationKindIsVar(declaredKind)) {
// Any vars that are redeclared as body-level functions must
// be recorded as body-level functions.
//
// In the case of global and eval scripts, GlobalDeclaration-
// Instantiation [1] and EvalDeclarationInstantiation [2]
// check for the declarability of global var and function
// bindings via CanDeclareVar [3] and CanDeclareGlobal-
// Function [4]. CanDeclareGlobalFunction is strictly more
// restrictive than CanDeclareGlobalVar, so record the more
// restrictive kind. These semantics are implemented in
// CheckCanDeclareGlobalBinding.
//
// For a var previously declared as ForOfVar, this previous
// DeclarationKind is used only to check for if the
// 'arguments' binding should be declared. Since body-level
// functions shadow 'arguments' [5], it is correct to alter
// the kind to BodyLevelFunction. See
// declareFunctionArgumentsObject.
//
// For a var previously declared as
// VarForAnnexBLexicalFunction, this previous DeclarationKind
// is used so that vars synthesized solely for Annex B.3.3 may
// be removed if an early error would occur. If a synthesized
// Annex B.3.3 var has the same name as a body-level function,
// this is not a redeclaration, and indeed, because the
// body-level function binds the name, this name should not be
// removed should a redeclaration occur in the future. Thus it
// is also correct to alter the kind to BodyLevelFunction.
//
// [1] ES 15.1.11
// [2] ES 18.2.1.3
// [3] ES 8.1.1.4.15
// [4] ES 8.1.1.4.16
// [5] ES 9.2.12
if (kind == DeclarationKind::BodyLevelFunction)
p->value()->alterKind(kind);
} else if (!DeclarationKindIsParameter(declaredKind)) {
// Annex B.3.5 allows redeclaring simple (non-destructured)
// catch parameters with var declarations, except when it
// appears in a for-of.
bool annexB35Allowance = declaredKind == DeclarationKind::SimpleCatchParameter &&
kind != DeclarationKind::ForOfVar;
// Annex B.3.3 allows redeclaring functions in the same block.
bool annexB33Allowance = declaredKind == DeclarationKind::LexicalFunction &&
kind == DeclarationKind::VarForAnnexBLexicalFunction &&
scope == pc->innermostScope();
if (!annexB35Allowance && !annexB33Allowance) {
*redeclaredKind = Some(declaredKind);
return true;
}
}
} else {
if (!scope->addDeclaredName(pc, p, name, kind))
return false;
}
}
if (!pc->sc()->strict() && pc->sc()->isEvalContext())
*redeclaredKind = isVarRedeclaredInEval(name, kind);
return true;
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::tryDeclareVarForAnnexBLexicalFunction(HandlePropertyName name,
bool* tryAnnexB)
{
Maybe<DeclarationKind> redeclaredKind;
if (!tryDeclareVar(name, DeclarationKind::VarForAnnexBLexicalFunction, &redeclaredKind))
return false;
if (redeclaredKind) {
// If an early error would have occurred, undo all the
// VarForAnnexBLexicalFunction declarations.
*tryAnnexB = false;
ParseContext::Scope::removeVarForAnnexBLexicalFunction(pc, name);
} else {
*tryAnnexB = true;
}
return true;
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::checkLexicalDeclarationDirectlyWithinBlock(ParseContext::Statement& stmt,
DeclarationKind kind,
TokenPos pos)
{
MOZ_ASSERT(DeclarationKindIsLexical(kind));
// It is an early error to declare a lexical binding not directly
// within a block.
if (!StatementKindIsBraced(stmt.kind()) &&
stmt.kind() != StatementKind::ForLoopLexicalHead)
{
errorAt(pos.begin,
stmt.kind() == StatementKind::Label
? JSMSG_LEXICAL_DECL_LABEL
: JSMSG_LEXICAL_DECL_NOT_IN_BLOCK,
DeclarationKindString(kind));
return false;
}
return true;
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::noteDeclaredName(HandlePropertyName name, DeclarationKind kind,
TokenPos pos)
{
// The asm.js validator does all its own symbol-table management so, as an
// optimization, avoid doing any work here.
if (pc->useAsmOrInsideUseAsm())
return true;
if (!checkStrictBinding(name, pos))
return false;
switch (kind) {
case DeclarationKind::Var:
case DeclarationKind::BodyLevelFunction:
case DeclarationKind::ForOfVar: {
Maybe<DeclarationKind> redeclaredKind;
if (!tryDeclareVar(name, kind, &redeclaredKind))
return false;
if (redeclaredKind) {
reportRedeclaration(name, *redeclaredKind, pos);
return false;
}
break;
}
case DeclarationKind::FormalParameter: {
// It is an early error if any non-positional formal parameter name
// (e.g., destructuring formal parameter) is duplicated.
AddDeclaredNamePtr p = pc->functionScope().lookupDeclaredNameForAdd(name);
if (p) {
error(JSMSG_BAD_DUP_ARGS);
return false;
}
if (!pc->functionScope().addDeclaredName(pc, p, name, kind))
return false;
break;
}
case DeclarationKind::LexicalFunction: {
// Functions in block have complex allowances in sloppy mode for being
// labelled that other lexical declarations do not have. Those checks
// are more complex than calling checkLexicalDeclarationDirectlyWithin-
// Block and are done inline in callers.
ParseContext::Scope* scope = pc->innermostScope();
if (AddDeclaredNamePtr p = scope->lookupDeclaredNameForAdd(name)) {
// It is usually an early error if there is another declaration
// with the same name in the same scope.
//
// In sloppy mode, lexical functions may redeclare other lexical
// functions for web compatibility reasons.
if (pc->sc()->strict() ||
(p->value()->kind() != DeclarationKind::LexicalFunction &&
p->value()->kind() != DeclarationKind::VarForAnnexBLexicalFunction))
{
reportRedeclaration(name, p->value()->kind(), pos);
return false;
}
// Update the DeclarationKind to make a LexicalFunction
// declaration that shadows the VarForAnnexBLexicalFunction.
p->value()->alterKind(kind);
} else {
if (!scope->addDeclaredName(pc, p, name, kind))
return false;
}
break;
}
case DeclarationKind::Let:
case DeclarationKind::Const:
// The BoundNames of LexicalDeclaration and ForDeclaration must not
// contain 'let'. (CatchParameter is the only lexical binding form
// without this restriction.)
if (name == context->names().let) {
errorAt(pos.begin, JSMSG_LEXICAL_DECL_DEFINES_LET);
return false;
}
MOZ_FALLTHROUGH;
case DeclarationKind::Import:
// Module code is always strict, so 'let' is always a keyword and never a name.
MOZ_ASSERT(name != context->names().let);
MOZ_FALLTHROUGH;
case DeclarationKind::SimpleCatchParameter:
case DeclarationKind::CatchParameter: {
if (ParseContext::Statement* stmt = pc->innermostStatement()) {
if (!checkLexicalDeclarationDirectlyWithinBlock(*stmt, kind, pos))
return false;
}
ParseContext::Scope* scope = pc->innermostScope();
// For body-level lexically declared names in a function, it is an
// early error if there is a formal parameter of the same name. This
// needs a special check if there is an extra var scope due to
// parameter expressions.
if (pc->isFunctionExtraBodyVarScopeInnermost()) {
DeclaredNamePtr p = pc->functionScope().lookupDeclaredName(name);
if (p && DeclarationKindIsParameter(p->value()->kind())) {
reportRedeclaration(name, p->value()->kind(), pos);
return false;
}
}
// It is an early error if there is another declaration with the same
// name in the same scope.
AddDeclaredNamePtr p = scope->lookupDeclaredNameForAdd(name);
if (p) {
// If the early error would have occurred due to Annex B.3.3
// semantics, remove the synthesized Annex B var declaration, do
// not report the redeclaration, and declare the lexical name.
if (p->value()->kind() == DeclarationKind::VarForAnnexBLexicalFunction) {
ParseContext::Scope::removeVarForAnnexBLexicalFunction(pc, name);
p = scope->lookupDeclaredNameForAdd(name);
MOZ_ASSERT(!p);
} else {
reportRedeclaration(name, p->value()->kind(), pos);
return false;
}
}
if (!p && !scope->addDeclaredName(pc, p, name, kind))
return false;
break;
}
case DeclarationKind::CoverArrowParameter:
// CoverArrowParameter is only used as a placeholder declaration kind.
break;
case DeclarationKind::PositionalFormalParameter:
MOZ_CRASH("Positional formal parameter names should use "
"notePositionalFormalParameter");
break;
case DeclarationKind::VarForAnnexBLexicalFunction:
MOZ_CRASH("Synthesized Annex B vars should go through "
"tryDeclareVarForAnnexBLexicalFunction");
break;
}
return true;
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::noteUsedName(HandlePropertyName name)
{
// If the we are delazifying, the LazyScript already has all the
// closed-over info for bindings and there's no need to track used names.
if (handler.canSkipLazyClosedOverBindings())
return true;
// The asm.js validator does all its own symbol-table management so, as an
// optimization, avoid doing any work here.
if (pc->useAsmOrInsideUseAsm())
return true;
// Global bindings are properties and not actual bindings; we don't need
// to know if they are closed over. So no need to track used name at the
// global scope. It is not incorrect to track them, this is an
// optimization.
ParseContext::Scope* scope = pc->innermostScope();
if (pc->sc()->isGlobalContext() && scope == &pc->varScope())
return true;
return usedNames.noteUse(context, name, pc->scriptId(), scope->id());
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::hasUsedName(HandlePropertyName name)
{
if (UsedNamePtr p = usedNames.lookup(name))
return p->value().isUsedInScript(pc->scriptId());
return false;
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::propagateFreeNamesAndMarkClosedOverBindings(ParseContext::Scope& scope)
{
if (handler.canSkipLazyClosedOverBindings()) {
// Scopes are nullptr-delimited in the LazyScript closed over bindings
// array.
while (JSAtom* name = handler.nextLazyClosedOverBinding())
scope.lookupDeclaredName(name)->value()->setClosedOver();
return true;
}
bool isSyntaxParser = mozilla::IsSame<ParseHandler, SyntaxParseHandler>::value;
uint32_t scriptId = pc->scriptId();
uint32_t scopeId = scope.id();
for (BindingIter bi = scope.bindings(pc); bi; bi++) {
if (UsedNamePtr p = usedNames.lookup(bi.name())) {
bool closedOver;
p->value().noteBoundInScope(scriptId, scopeId, &closedOver);
if (closedOver) {
bi.setClosedOver();
if (isSyntaxParser && !pc->closedOverBindingsForLazy().append(bi.name())) {
ReportOutOfMemory(context);
return false;
}
}
}
}
// Append a nullptr to denote end-of-scope.
if (isSyntaxParser && !pc->closedOverBindingsForLazy().append(nullptr)) {
ReportOutOfMemory(context);
return false;
}
return true;
}
template <>
bool
Parser<FullParseHandler>::checkStatementsEOF()
{
// This is designed to be paired with parsing a statement list at the top
// level.
//
// The statementList() call breaks on TOK_RC, so make sure we've
// reached EOF here.
TokenKind tt;
if (!tokenStream.peekToken(&tt, TokenStream::Operand))
return false;
if (tt != TOK_EOF) {
error(JSMSG_UNEXPECTED_TOKEN, "expression", TokenKindToDesc(tt));
return false;
}
return true;
}
template <typename Scope>
static typename Scope::Data*
NewEmptyBindingData(ExclusiveContext* cx, LifoAlloc& alloc, uint32_t numBindings)
{
using Data = typename Scope::Data;
size_t allocSize = Scope::sizeOfData(numBindings);
auto* bindings = alloc.allocInSize<Data>(allocSize, numBindings);
if (!bindings)
ReportOutOfMemory(cx);
return bindings;
}
/**
* Copy-construct |BindingName|s from |bindings| into |cursor|, then return
* the location one past the newly-constructed |BindingName|s.
*/
static MOZ_MUST_USE BindingName*
FreshlyInitializeBindings(BindingName* cursor, const Vector<BindingName>& bindings)
{
for (const BindingName& binding : bindings)
new (cursor++) BindingName(binding);
return cursor;
}
template <>
Maybe<GlobalScope::Data*>
Parser<FullParseHandler>::newGlobalScopeData(ParseContext::Scope& scope)
{
Vector<BindingName> funs(context);
Vector<BindingName> vars(context);
Vector<BindingName> lets(context);
Vector<BindingName> consts(context);
bool allBindingsClosedOver = pc->sc()->allBindingsClosedOver();
for (BindingIter bi = scope.bindings(pc); bi; bi++) {
BindingName binding(bi.name(), allBindingsClosedOver || bi.closedOver());
switch (bi.kind()) {
case BindingKind::Var:
if (bi.declarationKind() == DeclarationKind::BodyLevelFunction) {
if (!funs.append(binding))
return Nothing();
} else {
if (!vars.append(binding))
return Nothing();
}
break;
case BindingKind::Let:
if (!lets.append(binding))
return Nothing();
break;
case BindingKind::Const:
if (!consts.append(binding))
return Nothing();
break;
default:
MOZ_CRASH("Bad global scope BindingKind");
}
}
GlobalScope::Data* bindings = nullptr;
uint32_t numBindings = funs.length() + vars.length() + lets.length() + consts.length();
if (numBindings > 0) {
bindings = NewEmptyBindingData<GlobalScope>(context, alloc, numBindings);
if (!bindings)
return Nothing();
// The ordering here is important. See comments in GlobalScope.
BindingName* start = bindings->trailingNames.start();
BindingName* cursor = start;
cursor = FreshlyInitializeBindings(cursor, funs);
bindings->varStart = cursor - start;
cursor = FreshlyInitializeBindings(cursor, vars);
bindings->letStart = cursor - start;
cursor = FreshlyInitializeBindings(cursor, lets);
bindings->constStart = cursor - start;
cursor = FreshlyInitializeBindings(cursor, consts);
bindings->length = numBindings;
}
return Some(bindings);
}
template <>
Maybe<ModuleScope::Data*>
Parser<FullParseHandler>::newModuleScopeData(ParseContext::Scope& scope)
{
Vector<BindingName> imports(context);
Vector<BindingName> vars(context);
Vector<BindingName> lets(context);
Vector<BindingName> consts(context);
bool allBindingsClosedOver = pc->sc()->allBindingsClosedOver();
for (BindingIter bi = scope.bindings(pc); bi; bi++) {
// Imports are indirect bindings and must not be given known slots.
BindingName binding(bi.name(), (allBindingsClosedOver || bi.closedOver()) &&
bi.kind() != BindingKind::Import);
switch (bi.kind()) {
case BindingKind::Import:
if (!imports.append(binding))
return Nothing();
break;
case BindingKind::Var:
if (!vars.append(binding))
return Nothing();
break;
case BindingKind::Let:
if (!lets.append(binding))
return Nothing();
break;
case BindingKind::Const:
if (!consts.append(binding))
return Nothing();
break;
default:
MOZ_CRASH("Bad module scope BindingKind");
}
}
ModuleScope::Data* bindings = nullptr;
uint32_t numBindings = imports.length() + vars.length() + lets.length() + consts.length();
if (numBindings > 0) {
bindings = NewEmptyBindingData<ModuleScope>(context, alloc, numBindings);
if (!bindings)
return Nothing();
// The ordering here is important. See comments in ModuleScope.
BindingName* start = bindings->trailingNames.start();
BindingName* cursor = start;
cursor = FreshlyInitializeBindings(cursor, imports);
bindings->varStart = cursor - start;
cursor = FreshlyInitializeBindings(cursor, vars);
bindings->letStart = cursor - start;
cursor = FreshlyInitializeBindings(cursor, lets);
bindings->constStart = cursor - start;
cursor = FreshlyInitializeBindings(cursor, consts);
bindings->length = numBindings;
}
return Some(bindings);
}
template <>
Maybe<EvalScope::Data*>
Parser<FullParseHandler>::newEvalScopeData(ParseContext::Scope& scope)
{
Vector<BindingName> funs(context);
Vector<BindingName> vars(context);
for (BindingIter bi = scope.bindings(pc); bi; bi++) {
// Eval scopes only contain 'var' bindings. Make all bindings aliased
// for now.
MOZ_ASSERT(bi.kind() == BindingKind::Var);
BindingName binding(bi.name(), true);
if (bi.declarationKind() == DeclarationKind::BodyLevelFunction) {
if (!funs.append(binding))
return Nothing();
} else {
if (!vars.append(binding))
return Nothing();
}
}
EvalScope::Data* bindings = nullptr;
uint32_t numBindings = funs.length() + vars.length();
if (numBindings > 0) {
bindings = NewEmptyBindingData<EvalScope>(context, alloc, numBindings);
if (!bindings)
return Nothing();
BindingName* start = bindings->trailingNames.start();
BindingName* cursor = start;
// Keep track of what vars are functions. This is only used in BCE to omit
// superfluous DEFVARs.
cursor = FreshlyInitializeBindings(cursor, funs);
bindings->varStart = cursor - start;
cursor = FreshlyInitializeBindings(cursor, vars);
bindings->length = numBindings;
}
return Some(bindings);
}
template <>
Maybe<FunctionScope::Data*>
Parser<FullParseHandler>::newFunctionScopeData(ParseContext::Scope& scope, bool hasParameterExprs)
{
Vector<BindingName> positionalFormals(context);
Vector<BindingName> formals(context);
Vector<BindingName> vars(context);
bool allBindingsClosedOver = pc->sc()->allBindingsClosedOver();
bool hasDuplicateParams = pc->functionBox()->hasDuplicateParameters;
// Positional parameter names must be added in order of appearance as they are
// referenced using argument slots.
for (size_t i = 0; i < pc->positionalFormalParameterNames().length(); i++) {
JSAtom* name = pc->positionalFormalParameterNames()[i];
BindingName bindName;
if (name) {
DeclaredNamePtr p = scope.lookupDeclaredName(name);
// Do not consider any positional formal parameters closed over if
// there are parameter defaults. It is the binding in the defaults
// scope that is closed over instead.
bool closedOver = allBindingsClosedOver ||
(p && p->value()->closedOver());
// If the parameter name has duplicates, only the final parameter
// name should be on the environment, as otherwise the environment
// object would have multiple, same-named properties.
if (hasDuplicateParams) {
for (size_t j = pc->positionalFormalParameterNames().length() - 1; j > i; j--) {
if (pc->positionalFormalParameterNames()[j] == name) {
closedOver = false;
break;
}
}
}
bindName = BindingName(name, closedOver);
}
if (!positionalFormals.append(bindName))
return Nothing();
}
for (BindingIter bi = scope.bindings(pc); bi; bi++) {
BindingName binding(bi.name(), allBindingsClosedOver || bi.closedOver());
switch (bi.kind()) {
case BindingKind::FormalParameter:
// Positional parameter names are already handled above.
if (bi.declarationKind() == DeclarationKind::FormalParameter) {
if (!formals.append(binding))
return Nothing();
}
break;
case BindingKind::Var:
// The only vars in the function scope when there are parameter
// exprs, which induces a separate var environment, should be the
// special internal bindings.
MOZ_ASSERT_IF(hasParameterExprs,
bi.name() == context->names().arguments ||
bi.name() == context->names().dotThis ||
bi.name() == context->names().dotGenerator);
if (!vars.append(binding))
return Nothing();
break;
default:
break;
}
}
FunctionScope::Data* bindings = nullptr;
uint32_t numBindings = positionalFormals.length() + formals.length() + vars.length();
if (numBindings > 0) {
bindings = NewEmptyBindingData<FunctionScope>(context, alloc, numBindings);
if (!bindings)
return Nothing();
// The ordering here is important. See comments in FunctionScope.
BindingName* start = bindings->trailingNames.start();
BindingName* cursor = start;
cursor = FreshlyInitializeBindings(cursor, positionalFormals);
bindings->nonPositionalFormalStart = cursor - start;
cursor = FreshlyInitializeBindings(cursor, formals);
bindings->varStart = cursor - start;
cursor = FreshlyInitializeBindings(cursor, vars);
bindings->length = numBindings;
}
return Some(bindings);
}
template <>
Maybe<VarScope::Data*>
Parser<FullParseHandler>::newVarScopeData(ParseContext::Scope& scope)
{
Vector<BindingName> vars(context);
bool allBindingsClosedOver = pc->sc()->allBindingsClosedOver();
for (BindingIter bi = scope.bindings(pc); bi; bi++) {
BindingName binding(bi.name(), allBindingsClosedOver || bi.closedOver());
if (!vars.append(binding))
return Nothing();
}
VarScope::Data* bindings = nullptr;
uint32_t numBindings = vars.length();
if (numBindings > 0) {
bindings = NewEmptyBindingData<VarScope>(context, alloc, numBindings);
if (!bindings)
return Nothing();
// The ordering here is important. See comments in FunctionScope.
BindingName* start = bindings->trailingNames.start();
BindingName* cursor = start;
cursor = FreshlyInitializeBindings(cursor, vars);
bindings->length = numBindings;
}
return Some(bindings);
}
template <>
Maybe<LexicalScope::Data*>
Parser<FullParseHandler>::newLexicalScopeData(ParseContext::Scope& scope)
{
Vector<BindingName> lets(context);
Vector<BindingName> consts(context);
// Unlike other scopes with bindings which are body-level, it is unknown
// if pc->sc()->allBindingsClosedOver() is correct at the time of
// finishing parsing a lexical scope.
//
// Instead, pc->sc()->allBindingsClosedOver() is checked in
// EmitterScope::enterLexical. Also see comment there.
for (BindingIter bi = scope.bindings(pc); bi; bi++) {
BindingName binding(bi.name(), bi.closedOver());
switch (bi.kind()) {
case BindingKind::Let:
if (!lets.append(binding))
return Nothing();
break;
case BindingKind::Const:
if (!consts.append(binding))
return Nothing();
break;
default:
break;
}
}
LexicalScope::Data* bindings = nullptr;
uint32_t numBindings = lets.length() + consts.length();
if (numBindings > 0) {
bindings = NewEmptyBindingData<LexicalScope>(context, alloc, numBindings);
if (!bindings)
return Nothing();
// The ordering here is important. See comments in LexicalScope.
BindingName* cursor = bindings->trailingNames.start();
BindingName* start = cursor;
cursor = FreshlyInitializeBindings(cursor, lets);
bindings->constStart = cursor - start;
cursor = FreshlyInitializeBindings(cursor, consts);
bindings->length = numBindings;
}
return Some(bindings);
}
template <>
SyntaxParseHandler::Node
Parser<SyntaxParseHandler>::finishLexicalScope(ParseContext::Scope& scope, Node body)
{
if (!propagateFreeNamesAndMarkClosedOverBindings(scope))
return null();
return body;
}
template <>
ParseNode*
Parser<FullParseHandler>::finishLexicalScope(ParseContext::Scope& scope, ParseNode* body)
{
if (!propagateFreeNamesAndMarkClosedOverBindings(scope))
return nullptr;
Maybe<LexicalScope::Data*> bindings = newLexicalScopeData(scope);
if (!bindings)
return nullptr;
return handler.newLexicalScope(*bindings, body);
}
static bool
IsArgumentsUsedInLegacyGenerator(ExclusiveContext* cx, Scope* scope)
{
JSAtom* argumentsName = cx->names().arguments;
for (ScopeIter si(scope); si; si++) {
if (si.scope()->is<LexicalScope>()) {
// Using a shadowed lexical 'arguments' is okay.
for (::BindingIter bi(si.scope()); bi; bi++) {
if (bi.name() == argumentsName)
return false;
}
} else if (si.scope()->is<FunctionScope>()) {
// It's an error to use 'arguments' in a legacy generator expression.
JSScript* script = si.scope()->as<FunctionScope>().script();
return script->isGeneratorExp() && script->isLegacyGenerator();
}
}
return false;
}
template <>
ParseNode*
Parser<FullParseHandler>::evalBody(EvalSharedContext* evalsc)
{
ParseContext evalpc(this, evalsc, /* newDirectives = */ nullptr);
if (!evalpc.init())
return nullptr;
ParseContext::VarScope varScope(this);
if (!varScope.init(pc))
return nullptr;
// All evals have an implicit non-extensible lexical scope.
ParseContext::Scope lexicalScope(this);
if (!lexicalScope.init(pc))
return nullptr;
ParseNode* body = statementList(YieldIsName);
if (!body)
return nullptr;
if (!checkStatementsEOF())
return nullptr;
body = finishLexicalScope(lexicalScope, body);
if (!body)
return nullptr;
// It's an error to use 'arguments' in a legacy generator expression.
//
// If 'arguments' appears free (i.e. not a declared name) or if the
// declaration does not shadow the enclosing script's 'arguments'
// binding (i.e. not a lexical declaration), check the enclosing
// script.
if (hasUsedName(context->names().arguments)) {
if (IsArgumentsUsedInLegacyGenerator(context, pc->sc()->compilationEnclosingScope())) {
error(JSMSG_BAD_GENEXP_BODY, js_arguments_str);
return nullptr;
}
}
#ifdef DEBUG
if (evalpc.superScopeNeedsHomeObject() && evalsc->compilationEnclosingScope()) {
// If superScopeNeedsHomeObject_ is set and we are an entry-point
// ParseContext, then we must be emitting an eval script, and the
// outer function must already be marked as needing a home object
// since it contains an eval.
ScopeIter si(evalsc->compilationEnclosingScope());
for (; si; si++) {
if (si.kind() == ScopeKind::Function) {
JSFunction* fun = si.scope()->as<FunctionScope>().canonicalFunction();
if (fun->isArrow())
continue;
MOZ_ASSERT(fun->allowSuperProperty());
MOZ_ASSERT(fun->nonLazyScript()->needsHomeObject());
break;
}
}
MOZ_ASSERT(!si.done(),
"Eval must have found an enclosing function box scope that allows super.property");
}
#endif
if (!FoldConstants(context, &body, this))
return nullptr;
Maybe<EvalScope::Data*> bindings = newEvalScopeData(pc->varScope());
if (!bindings)
return nullptr;
evalsc->bindings = *bindings;
return body;
}
template <>
ParseNode*
Parser<FullParseHandler>::globalBody(GlobalSharedContext* globalsc)
{
ParseContext globalpc(this, globalsc, /* newDirectives = */ nullptr);
if (!globalpc.init())
return nullptr;
ParseContext::VarScope varScope(this);
if (!varScope.init(pc))
return nullptr;
ParseNode* body = statementList(YieldIsName);
if (!body)
return nullptr;
if (!checkStatementsEOF())
return nullptr;
if (!FoldConstants(context, &body, this))
return nullptr;
Maybe<GlobalScope::Data*> bindings = newGlobalScopeData(pc->varScope());
if (!bindings)
return nullptr;
globalsc->bindings = *bindings;
return body;
}
template <>
ParseNode*
Parser<FullParseHandler>::moduleBody(ModuleSharedContext* modulesc)
{
MOZ_ASSERT(checkOptionsCalled);
ParseContext modulepc(this, modulesc, nullptr);
if (!modulepc.init())
return null();
ParseContext::VarScope varScope(this);
if (!varScope.init(pc))
return nullptr;
Node mn = handler.newModule();
if (!mn)
return null();
AutoAwaitIsKeyword awaitIsKeyword(&tokenStream, true);
ParseNode* pn = statementList(YieldIsKeyword);
if (!pn)
return null();
MOZ_ASSERT(pn->isKind(PNK_STATEMENTLIST));
mn->pn_body = pn;
TokenKind tt;
if (!tokenStream.getToken(&tt, TokenStream::Operand))
return null();
if (tt != TOK_EOF) {
error(JSMSG_GARBAGE_AFTER_INPUT, "module", TokenKindToDesc(tt));
return null();
}
if (!modulesc->builder.buildTables())
return null();
// Check exported local bindings exist and mark them as closed over.
for (auto entry : modulesc->builder.localExportEntries()) {
JSAtom* name = entry->localName();
MOZ_ASSERT(name);
DeclaredNamePtr p = modulepc.varScope().lookupDeclaredName(name);
if (!p) {
JSAutoByteString str;
if (!str.encodeLatin1(context, name))
return null();
JS_ReportErrorNumberLatin1(context->asJSContext(), GetErrorMessage, nullptr,
JSMSG_MISSING_EXPORT, str.ptr());
return null();
}
p->value()->setClosedOver();
}
if (!FoldConstants(context, &pn, this))
return null();
if (!propagateFreeNamesAndMarkClosedOverBindings(modulepc.varScope()))
return null();
Maybe<ModuleScope::Data*> bindings = newModuleScopeData(modulepc.varScope());
if (!bindings)
return nullptr;
modulesc->bindings = *bindings;
return mn;
}
template <>
SyntaxParseHandler::Node
Parser<SyntaxParseHandler>::moduleBody(ModuleSharedContext* modulesc)
{
MOZ_ALWAYS_FALSE(abortIfSyntaxParser());
return SyntaxParseHandler::NodeFailure;
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::hasUsedFunctionSpecialName(HandlePropertyName name)
{
MOZ_ASSERT(name == context->names().arguments || name == context->names().dotThis);
return hasUsedName(name) || pc->functionBox()->bindingsAccessedDynamically();
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::declareFunctionThis()
{
// The asm.js validator does all its own symbol-table management so, as an
// optimization, avoid doing any work here.
if (pc->useAsmOrInsideUseAsm())
return true;
// Derived class constructors emit JSOP_CHECKRETURN, which requires
// '.this' to be bound.
FunctionBox* funbox = pc->functionBox();
HandlePropertyName dotThis = context->names().dotThis;
bool declareThis;
if (handler.canSkipLazyClosedOverBindings())
declareThis = funbox->function()->lazyScript()->hasThisBinding();
else
declareThis = hasUsedFunctionSpecialName(dotThis) || funbox->isDerivedClassConstructor();
if (declareThis) {
ParseContext::Scope& funScope = pc->functionScope();
AddDeclaredNamePtr p = funScope.lookupDeclaredNameForAdd(dotThis);
MOZ_ASSERT(!p);
if (!funScope.addDeclaredName(pc, p, dotThis, DeclarationKind::Var))
return false;
funbox->setHasThisBinding();
}
return true;
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::newInternalDotName(HandlePropertyName name)
{
Node nameNode = newName(name);
if (!nameNode)
return null();
if (!noteUsedName(name))
return null();
return nameNode;
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::newThisName()
{
return newInternalDotName(context->names().dotThis);
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::newDotGeneratorName()
{
return newInternalDotName(context->names().dotGenerator);
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::declareDotGeneratorName()
{
// The special '.generator' binding must be on the function scope, as
// generators expect to find it on the CallObject.
ParseContext::Scope& funScope = pc->functionScope();
HandlePropertyName dotGenerator = context->names().dotGenerator;
AddDeclaredNamePtr p = funScope.lookupDeclaredNameForAdd(dotGenerator);
if (!p && !funScope.addDeclaredName(pc, p, dotGenerator, DeclarationKind::Var))
return false;
return true;
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::finishFunctionScopes()
{
FunctionBox* funbox = pc->functionBox();
if (funbox->hasParameterExprs) {
if (!propagateFreeNamesAndMarkClosedOverBindings(pc->functionScope()))
return false;
}
if (funbox->function()->isNamedLambda()) {
if (!propagateFreeNamesAndMarkClosedOverBindings(pc->namedLambdaScope()))
return false;
}
return true;
}
template <>
bool
Parser<FullParseHandler>::finishFunction()
{
if (!finishFunctionScopes())
return false;
FunctionBox* funbox = pc->functionBox();
bool hasParameterExprs = funbox->hasParameterExprs;
if (hasParameterExprs) {
Maybe<VarScope::Data*> bindings = newVarScopeData(pc->varScope());
if (!bindings)
return false;
funbox->extraVarScopeBindings().set(*bindings);
}
{
Maybe<FunctionScope::Data*> bindings = newFunctionScopeData(pc->functionScope(),
hasParameterExprs);
if (!bindings)
return false;
funbox->functionScopeBindings().set(*bindings);
}
if (funbox->function()->isNamedLambda()) {
Maybe<LexicalScope::Data*> bindings = newLexicalScopeData(pc->namedLambdaScope());
if (!bindings)
return false;
funbox->namedLambdaBindings().set(*bindings);
}
return true;
}
template <>
bool
Parser<SyntaxParseHandler>::finishFunction()
{
// The LazyScript for a lazily parsed function needs to know its set of
// free variables and inner functions so that when it is fully parsed, we
// can skip over any already syntax parsed inner functions and still
// retain correct scope information.
if (!finishFunctionScopes())
return false;
// There are too many bindings or inner functions to be saved into the
// LazyScript. Do a full parse.
if (pc->closedOverBindingsForLazy().length() >= LazyScript::NumClosedOverBindingsLimit ||
pc->innerFunctionsForLazy.length() >= LazyScript::NumInnerFunctionsLimit)
{
MOZ_ALWAYS_FALSE(abortIfSyntaxParser());
return false;
}
FunctionBox* funbox = pc->functionBox();
RootedFunction fun(context, funbox->function());
LazyScript* lazy = LazyScript::Create(context, fun, pc->closedOverBindingsForLazy(),
pc->innerFunctionsForLazy, versionNumber(),
funbox->bufStart, funbox->bufEnd,
funbox->preludeStart,
funbox->startLine, funbox->startColumn);
if (!lazy)
return false;
// Flags that need to be copied into the JSScript when we do the full
// parse.
if (pc->sc()->strict())
lazy->setStrict();
lazy->setGeneratorKind(funbox->generatorKind());
lazy->setAsyncKind(funbox->asyncKind());
if (funbox->hasRest())
lazy->setHasRest();
if (funbox->isLikelyConstructorWrapper())
lazy->setLikelyConstructorWrapper();
if (funbox->isDerivedClassConstructor())
lazy->setIsDerivedClassConstructor();
if (funbox->needsHomeObject())
lazy->setNeedsHomeObject();
if (funbox->declaredArguments)
lazy->setShouldDeclareArguments();
if (funbox->hasThisBinding())
lazy->setHasThisBinding();
// Flags that need to copied back into the parser when we do the full
// parse.
PropagateTransitiveParseFlags(funbox, lazy);
fun->initLazyScript(lazy);
return true;
}
static YieldHandling
GetYieldHandling(GeneratorKind generatorKind, FunctionAsyncKind asyncKind)
{
if (asyncKind == AsyncFunction)
return YieldIsName;
if (generatorKind == NotGenerator)
return YieldIsName;
return YieldIsKeyword;
}
template <>
ParseNode*
Parser<FullParseHandler>::standaloneFunction(HandleFunction fun,
HandleScope enclosingScope,
Maybe<uint32_t> parameterListEnd,
GeneratorKind generatorKind,
FunctionAsyncKind asyncKind,
Directives inheritedDirectives,
Directives* newDirectives)
{
MOZ_ASSERT(checkOptionsCalled);
// Skip prelude.
TokenKind tt;
if (!tokenStream.getToken(&tt))
return null();
if (asyncKind == AsyncFunction) {
MOZ_ASSERT(tt == TOK_ASYNC);
if (!tokenStream.getToken(&tt))
return null();
}
MOZ_ASSERT(tt == TOK_FUNCTION);
if (!tokenStream.getToken(&tt))
return null();
if (generatorKind == StarGenerator && asyncKind == SyncFunction) {
MOZ_ASSERT(tt == TOK_MUL);
if (!tokenStream.getToken(&tt))
return null();
}
// Skip function name, if present.
if (tt == TOK_NAME || tt == TOK_YIELD) {
MOZ_ASSERT(tokenStream.currentName() == fun->explicitName());
} else {
MOZ_ASSERT(fun->explicitName() == nullptr);
tokenStream.ungetToken();
}
Node fn = handler.newFunctionStatement();
if (!fn)
return null();
ParseNode* argsbody = handler.newList(PNK_PARAMSBODY);
if (!argsbody)
return null();
fn->pn_body = argsbody;
FunctionBox* funbox = newFunctionBox(fn, fun, /* preludeStart = */ 0, inheritedDirectives,
generatorKind, asyncKind, /* tryAnnexB = */ false);
if (!funbox)
return null();
funbox->initStandaloneFunction(enclosingScope);
ParseContext funpc(this, funbox, newDirectives);
if (!funpc.init())
return null();
funpc.setIsStandaloneFunctionBody();
YieldHandling yieldHandling = GetYieldHandling(generatorKind, asyncKind);
AutoAwaitIsKeyword awaitIsKeyword(&tokenStream, asyncKind == AsyncFunction);
if (!functionFormalParametersAndBody(InAllowed, yieldHandling, fn, Statement,
parameterListEnd))
{
return null();
}
if (!tokenStream.getToken(&tt, TokenStream::Operand))
return null();
if (tt != TOK_EOF) {
error(JSMSG_GARBAGE_AFTER_INPUT, "function body", TokenKindToDesc(tt));
return null();
}
if (!FoldConstants(context, &fn, this))
return null();
return fn;
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::declareFunctionArgumentsObject()
{
FunctionBox* funbox = pc->functionBox();
ParseContext::Scope& funScope = pc->functionScope();
ParseContext::Scope& varScope = pc->varScope();
bool hasExtraBodyVarScope = &funScope != &varScope;
// Time to implement the odd semantics of 'arguments'.
HandlePropertyName argumentsName = context->names().arguments;
bool tryDeclareArguments;
if (handler.canSkipLazyClosedOverBindings())
tryDeclareArguments = funbox->function()->lazyScript()->shouldDeclareArguments();
else
tryDeclareArguments = hasUsedFunctionSpecialName(argumentsName);
// ES 9.2.12 steps 19 and 20 say formal parameters, lexical bindings,
// and body-level functions named 'arguments' shadow the arguments
// object.
//
// So even if there wasn't a free use of 'arguments' but there is a var
// binding of 'arguments', we still might need the arguments object.
//
// If we have an extra var scope due to parameter expressions and the body
// declared 'var arguments', we still need to declare 'arguments' in the
// function scope.
DeclaredNamePtr p = varScope.lookupDeclaredName(argumentsName);
if (p && (p->value()->kind() == DeclarationKind::Var ||
p->value()->kind() == DeclarationKind::ForOfVar))
{
if (hasExtraBodyVarScope)
tryDeclareArguments = true;
else
funbox->usesArguments = true;
}
if (tryDeclareArguments) {
AddDeclaredNamePtr p = funScope.lookupDeclaredNameForAdd(argumentsName);
if (!p) {
if (!funScope.addDeclaredName(pc, p, argumentsName, DeclarationKind::Var))
return false;
funbox->declaredArguments = true;
funbox->usesArguments = true;
} else if (hasExtraBodyVarScope) {
// Formal parameters shadow the arguments object.
return true;
}
}
// Compute if we need an arguments object.
if (funbox->usesArguments) {
// There is an 'arguments' binding. Is the arguments object definitely
// needed?
//
// Also see the flags' comments in ContextFlags.
funbox->setArgumentsHasLocalBinding();
// Dynamic scope access destroys all hope of optimization.
if (pc->sc()->bindingsAccessedDynamically())
funbox->setDefinitelyNeedsArgsObj();
// If a script contains the debugger statement either directly or
// within an inner function, the arguments object should be created
// eagerly so the Debugger API may observe bindings.
if (pc->sc()->hasDebuggerStatement())
funbox->setDefinitelyNeedsArgsObj();
}
return true;
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::functionBody(InHandling inHandling, YieldHandling yieldHandling,
FunctionSyntaxKind kind, FunctionBodyType type)
{
MOZ_ASSERT(pc->isFunctionBox());
MOZ_ASSERT(!pc->funHasReturnExpr && !pc->funHasReturnVoid);
#ifdef DEBUG
uint32_t startYieldOffset = pc->lastYieldOffset;
#endif
Node pn;
if (type == StatementListBody) {
bool inheritedStrict = pc->sc()->strict();
pn = statementList(yieldHandling);
if (!pn)
return null();
// When we transitioned from non-strict to strict mode, we need to
// validate that all parameter names are valid strict mode names.
if (!inheritedStrict && pc->sc()->strict()) {
MOZ_ASSERT(pc->sc()->hasExplicitUseStrict(),
"strict mode should only change when a 'use strict' directive is present");
if (!hasValidSimpleStrictParameterNames()) {
// Request that this function be reparsed as strict to report
// the invalid parameter name at the correct source location.
pc->newDirectives->setStrict();
return null();
}
}
} else {
MOZ_ASSERT(type == ExpressionBody);
// Async functions are implemented as star generators, and star
// generators are assumed to be statement lists, to prepend initial
// `yield`.
Node stmtList = null();
if (pc->isAsync()) {
stmtList = handler.newStatementList(pos());
if (!stmtList)
return null();
}
Node kid = assignExpr(inHandling, yieldHandling, TripledotProhibited);
if (!kid)
return null();
pn = handler.newReturnStatement(kid, handler.getPosition(kid));
if (!pn)
return null();
if (pc->isAsync()) {
handler.addStatementToList(stmtList, pn);
pn = stmtList;
}
}
switch (pc->generatorKind()) {
case NotGenerator:
MOZ_ASSERT(pc->lastYieldOffset == startYieldOffset);
break;
case LegacyGenerator:
MOZ_ASSERT(pc->lastYieldOffset != startYieldOffset);
// These should throw while parsing the yield expression.
MOZ_ASSERT(kind != Arrow);
MOZ_ASSERT(!IsGetterKind(kind));
MOZ_ASSERT(!IsSetterKind(kind));
MOZ_ASSERT(!IsConstructorKind(kind));
MOZ_ASSERT(kind != Method);
MOZ_ASSERT(type != ExpressionBody);
break;
case StarGenerator:
MOZ_ASSERT_IF(!pc->isAsync(), kind != Arrow);
MOZ_ASSERT_IF(!pc->isAsync(), type == StatementListBody);
break;
}
if (pc->isGenerator()) {
MOZ_ASSERT_IF(!pc->isAsync(), type == StatementListBody);
if (!declareDotGeneratorName())
return null();
Node generator = newDotGeneratorName();
if (!generator)
return null();
if (!handler.prependInitialYield(pn, generator))
return null();
}
// Declare the 'arguments' and 'this' bindings if necessary before
// finishing up the scope so these special bindings get marked as closed
// over if necessary. Arrow functions don't have these bindings.
if (kind != Arrow) {
if (!declareFunctionArgumentsObject())
return null();
if (!declareFunctionThis())
return null();
}
return finishLexicalScope(pc->varScope(), pn);
}
template <typename ParseHandler>
JSFunction*
Parser<ParseHandler>::newFunction(HandleAtom atom, FunctionSyntaxKind kind,
GeneratorKind generatorKind, FunctionAsyncKind asyncKind,
HandleObject proto)
{
MOZ_ASSERT_IF(kind == Statement, atom != nullptr);
RootedFunction fun(context);
gc::AllocKind allocKind = gc::AllocKind::FUNCTION;
JSFunction::Flags flags;
#ifdef DEBUG
bool isGlobalSelfHostedBuiltin = false;
#endif
switch (kind) {
case Expression:
flags = (generatorKind == NotGenerator
? JSFunction::INTERPRETED_LAMBDA
: JSFunction::INTERPRETED_LAMBDA_GENERATOR);
break;
case Arrow:
flags = JSFunction::INTERPRETED_LAMBDA_ARROW;
allocKind = gc::AllocKind::FUNCTION_EXTENDED;
break;
case Method:
MOZ_ASSERT(generatorKind == NotGenerator || generatorKind == StarGenerator);
flags = (generatorKind == NotGenerator
? JSFunction::INTERPRETED_METHOD
: JSFunction::INTERPRETED_METHOD_GENERATOR);
allocKind = gc::AllocKind::FUNCTION_EXTENDED;
break;
case ClassConstructor:
case DerivedClassConstructor:
flags = JSFunction::INTERPRETED_CLASS_CONSTRUCTOR;
allocKind = gc::AllocKind::FUNCTION_EXTENDED;
break;
case Getter:
case GetterNoExpressionClosure:
flags = JSFunction::INTERPRETED_GETTER;
allocKind = gc::AllocKind::FUNCTION_EXTENDED;
break;
case Setter:
case SetterNoExpressionClosure:
flags = JSFunction::INTERPRETED_SETTER;
allocKind = gc::AllocKind::FUNCTION_EXTENDED;
break;
default:
MOZ_ASSERT(kind == Statement);
#ifdef DEBUG
if (options().selfHostingMode && !pc->isFunctionBox()) {
isGlobalSelfHostedBuiltin = true;
allocKind = gc::AllocKind::FUNCTION_EXTENDED;
}
#endif
flags = (generatorKind == NotGenerator
? JSFunction::INTERPRETED_NORMAL
: JSFunction::INTERPRETED_GENERATOR);
}
// We store the async wrapper in a slot for later access.
if (asyncKind == AsyncFunction)
allocKind = gc::AllocKind::FUNCTION_EXTENDED;
fun = NewFunctionWithProto(context, nullptr, 0, flags, nullptr, atom, proto,
allocKind, TenuredObject);
if (!fun)
return nullptr;
if (options().selfHostingMode) {
fun->setIsSelfHostedBuiltin();
#ifdef DEBUG
if (isGlobalSelfHostedBuiltin)
fun->setExtendedSlot(HAS_SELFHOSTED_CANONICAL_NAME_SLOT, BooleanValue(false));
#endif
}
return fun;
}
/*
* WARNING: Do not call this function directly.
* Call either MatchOrInsertSemicolonAfterExpression or
* MatchOrInsertSemicolonAfterNonExpression instead, depending on context.
*/
static bool
MatchOrInsertSemicolonHelper(TokenStream& ts, TokenStream::Modifier modifier)
{
TokenKind tt = TOK_EOF;
if (!ts.peekTokenSameLine(&tt, modifier))
return false;
if (tt != TOK_EOF && tt != TOK_EOL && tt != TOK_SEMI && tt != TOK_RC) {
/* Advance the scanner for proper error location reporting. */
ts.consumeKnownToken(tt, modifier);
ts.reportError(JSMSG_SEMI_BEFORE_STMNT);
return false;
}
bool matched;
if (!ts.matchToken(&matched, TOK_SEMI, modifier))
return false;
if (!matched && modifier == TokenStream::None)
ts.addModifierException(TokenStream::OperandIsNone);
return true;
}
static bool
MatchOrInsertSemicolonAfterExpression(TokenStream& ts)
{
return MatchOrInsertSemicolonHelper(ts, TokenStream::None);
}
static bool
MatchOrInsertSemicolonAfterNonExpression(TokenStream& ts)
{
return MatchOrInsertSemicolonHelper(ts, TokenStream::Operand);
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::leaveInnerFunction(ParseContext* outerpc)
{
MOZ_ASSERT(pc != outerpc);
// If the current function allows super.property but cannot have a home
// object, i.e., it is an arrow function, we need to propagate the flag to
// the outer ParseContext.
if (pc->superScopeNeedsHomeObject()) {
if (!pc->isArrowFunction())
MOZ_ASSERT(pc->functionBox()->needsHomeObject());
else
outerpc->setSuperScopeNeedsHomeObject();
}
// Lazy functions inner to another lazy function need to be remembered by
// the inner function so that if the outer function is eventually parsed
// we do not need any further parsing or processing of the inner function.
//
// Append the inner function here unconditionally; the vector is only used
// if the Parser using outerpc is a syntax parsing. See
// Parser<SyntaxParseHandler>::finishFunction.
if (!outerpc->innerFunctionsForLazy.append(pc->functionBox()->function()))
return false;
PropagateTransitiveParseFlags(pc->functionBox(), outerpc->sc());
return true;
}
template <typename ParseHandler>
JSAtom*
Parser<ParseHandler>::prefixAccessorName(PropertyType propType, HandleAtom propAtom)
{
RootedAtom prefix(context);
if (propType == PropertyType::Setter || propType == PropertyType::SetterNoExpressionClosure) {
prefix = context->names().setPrefix;
} else {
MOZ_ASSERT(propType == PropertyType::Getter || propType == PropertyType::GetterNoExpressionClosure);
prefix = context->names().getPrefix;
}
RootedString str(context, ConcatStrings<CanGC>(context, prefix, propAtom));
if (!str)
return nullptr;
return AtomizeString(context, str);
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::functionArguments(YieldHandling yieldHandling, FunctionSyntaxKind kind,
Node funcpn)
{
FunctionBox* funbox = pc->functionBox();
bool parenFreeArrow = false;
// Modifier for the following tokens.
// TokenStream::None for the following cases:
// async a => 1
// ^
//
// (a) => 1
// ^
//
// async (a) => 1
// ^
//
// function f(a) {}
// ^
//
// TokenStream::Operand for the following case:
// a => 1
// ^
TokenStream::Modifier firstTokenModifier = TokenStream::None;
// Modifier for the the first token in each argument.
// can be changed to TokenStream::None for the following case:
// async a => 1
// ^
TokenStream::Modifier argModifier = TokenStream::Operand;
if (kind == Arrow) {
TokenKind tt;
// In async function, the first token after `async` is already gotten
// with TokenStream::None.
// In sync function, the first token is already gotten with
// TokenStream::Operand.
firstTokenModifier = funbox->isAsync() ? TokenStream::None : TokenStream::Operand;
if (!tokenStream.peekToken(&tt, firstTokenModifier))
return false;
if (tt == TOK_NAME || tt == TOK_YIELD) {
parenFreeArrow = true;
argModifier = firstTokenModifier;
}
}
if (!parenFreeArrow) {
TokenKind tt;
if (!tokenStream.getToken(&tt, firstTokenModifier))
return false;
if (tt != TOK_LP) {
error(kind == Arrow ? JSMSG_BAD_ARROW_ARGS : JSMSG_PAREN_BEFORE_FORMAL);
return false;
}
// Record the start of function source (for FunctionToString). If we
// are parenFreeArrow, we will set this below, after consuming the NAME.
funbox->setStart(tokenStream);
}
Node argsbody = handler.newList(PNK_PARAMSBODY);
if (!argsbody)
return false;
handler.setFunctionFormalParametersAndBody(funcpn, argsbody);
bool hasArguments = false;
if (parenFreeArrow) {
hasArguments = true;
} else {
bool matched;
if (!tokenStream.matchToken(&matched, TOK_RP, TokenStream::Operand))
return false;
if (!matched)
hasArguments = true;
}
if (hasArguments) {
bool hasRest = false;
bool hasDefault = false;
bool duplicatedParam = false;
bool disallowDuplicateParams = kind == Arrow || kind == Method || kind == ClassConstructor;
AtomVector& positionalFormals = pc->positionalFormalParameterNames();
if (IsGetterKind(kind)) {
error(JSMSG_ACCESSOR_WRONG_ARGS, "getter", "no", "s");
return false;
}
while (true) {
if (hasRest) {
error(JSMSG_PARAMETER_AFTER_REST);
return false;
}
TokenKind tt;
if (!tokenStream.getToken(&tt, argModifier))
return false;
argModifier = TokenStream::Operand;
MOZ_ASSERT_IF(parenFreeArrow, tt == TOK_NAME || tt == TOK_YIELD);
if (tt == TOK_TRIPLEDOT) {
if (IsSetterKind(kind)) {
error(JSMSG_ACCESSOR_WRONG_ARGS, "setter", "one", "");
return false;
}
disallowDuplicateParams = true;
if (duplicatedParam) {
// Has duplicated args before the rest parameter.
error(JSMSG_BAD_DUP_ARGS);
return false;
}
hasRest = true;
funbox->setHasRest();
if (!tokenStream.getToken(&tt))
return false;
if (tt != TOK_NAME && tt != TOK_YIELD && tt != TOK_LB && tt != TOK_LC) {
error(JSMSG_NO_REST_NAME);
return false;
}
}
switch (tt) {
case TOK_LB:
case TOK_LC: {
disallowDuplicateParams = true;
if (duplicatedParam) {
// Has duplicated args before the destructuring parameter.
error(JSMSG_BAD_DUP_ARGS);
return false;
}
funbox->hasDestructuringArgs = true;
Node destruct = destructuringDeclarationWithoutYieldOrAwait(
DeclarationKind::FormalParameter,
yieldHandling, tt);
if (!destruct)
return false;
if (!noteDestructuredPositionalFormalParameter(funcpn, destruct))
return false;
break;
}
case TOK_NAME:
case TOK_YIELD: {
if (parenFreeArrow)
funbox->setStart(tokenStream);
if (funbox->isAsync() && tokenStream.currentName() == context->names().await) {
// `await` is already gotten as TOK_NAME for the following
// case:
//
// async await => 1
error(JSMSG_RESERVED_ID, "await");
return false;
}
RootedPropertyName name(context, bindingIdentifier(yieldHandling));
if (!name)
return false;
if (!notePositionalFormalParameter(funcpn, name, disallowDuplicateParams,
&duplicatedParam))
{
return false;
}
if (duplicatedParam)
funbox->hasDuplicateParameters = true;
break;
}
default:
error(JSMSG_MISSING_FORMAL);
return false;
}
if (positionalFormals.length() >= ARGNO_LIMIT) {
error(JSMSG_TOO_MANY_FUN_ARGS);
return false;
}
bool matched;
if (!tokenStream.matchToken(&matched, TOK_ASSIGN))
return false;
if (matched) {
// A default argument without parentheses would look like:
// a = expr => body, but both operators are right-associative, so
// that would have been parsed as a = (expr => body) instead.
// Therefore it's impossible to get here with parenFreeArrow.
MOZ_ASSERT(!parenFreeArrow);
if (hasRest) {
error(JSMSG_REST_WITH_DEFAULT);
return false;
}
disallowDuplicateParams = true;
if (duplicatedParam) {
error(JSMSG_BAD_DUP_ARGS);
return false;
}
if (!hasDefault) {
hasDefault = true;
// The Function.length property is the number of formals
// before the first default argument.
funbox->length = positionalFormals.length() - 1;
}
funbox->hasParameterExprs = true;
Node def_expr = assignExprWithoutYieldOrAwait(yieldHandling);
if (!def_expr)
return false;
if (!handler.setLastFunctionFormalParameterDefault(funcpn, def_expr))
return false;
}
if (parenFreeArrow || IsSetterKind(kind))
break;
if (!tokenStream.matchToken(&matched, TOK_COMMA))
return false;
if (!matched)
break;
if (!hasRest) {
if (!tokenStream.peekToken(&tt, TokenStream::Operand))
return null();
if (tt == TOK_RP) {
tokenStream.addModifierException(TokenStream::NoneIsOperand);
break;
}
}
}
if (!parenFreeArrow) {
TokenKind tt;
if (!tokenStream.getToken(&tt))
return false;
if (tt != TOK_RP) {
if (IsSetterKind(kind)) {
error(JSMSG_ACCESSOR_WRONG_ARGS, "setter", "one", "");
return false;
}
error(JSMSG_PAREN_AFTER_FORMAL);
return false;
}
}
if (!hasDefault)
funbox->length = positionalFormals.length() - hasRest;
if (funbox->hasParameterExprs && funbox->hasDirectEval())
funbox->hasDirectEvalInParameterExpr = true;
funbox->function()->setArgCount(positionalFormals.length());
} else if (IsSetterKind(kind)) {
error(JSMSG_ACCESSOR_WRONG_ARGS, "setter", "one", "");
return false;
}
return true;
}
template <>
bool
Parser<FullParseHandler>::skipLazyInnerFunction(ParseNode* pn, uint32_t preludeStart,
FunctionSyntaxKind kind, bool tryAnnexB)
{
// When a lazily-parsed function is called, we only fully parse (and emit)
// that function, not any of its nested children. The initial syntax-only
// parse recorded the free variables of nested functions and their extents,
// so we can skip over them after accounting for their free variables.
RootedFunction fun(context, handler.nextLazyInnerFunction());
MOZ_ASSERT(!fun->isLegacyGenerator());
FunctionBox* funbox = newFunctionBox(pn, fun, preludeStart, Directives(/* strict = */ false),
fun->generatorKind(), fun->asyncKind(), tryAnnexB);
if (!funbox)
return false;
LazyScript* lazy = fun->lazyScript();
if (lazy->needsHomeObject())
funbox->setNeedsHomeObject();
PropagateTransitiveParseFlags(lazy, pc->sc());
// The position passed to tokenStream.advance() is an offset of the sort
// returned by userbuf.offset() and expected by userbuf.rawCharPtrAt(),
// while LazyScript::{begin,end} offsets are relative to the outermost
// script source.
Rooted<LazyScript*> lazyOuter(context, handler.lazyOuterFunction());
uint32_t userbufBase = lazyOuter->begin() - lazyOuter->column();
if (!tokenStream.advance(fun->lazyScript()->end() - userbufBase))
return false;
if (kind == Statement && fun->isExprBody()) {
if (!MatchOrInsertSemicolonAfterExpression(tokenStream))
return false;
}
return true;
}
template <>
bool
Parser<SyntaxParseHandler>::skipLazyInnerFunction(Node pn, uint32_t preludeStart,
FunctionSyntaxKind kind, bool tryAnnexB)
{
MOZ_CRASH("Cannot skip lazy inner functions when syntax parsing");
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::addExprAndGetNextTemplStrToken(YieldHandling yieldHandling, Node nodeList,
TokenKind* ttp)
{
Node pn = expr(InAllowed, yieldHandling, TripledotProhibited);
if (!pn)
return false;
handler.addList(nodeList, pn);
TokenKind tt;
if (!tokenStream.getToken(&tt))
return false;
if (tt != TOK_RC) {
error(JSMSG_TEMPLSTR_UNTERM_EXPR);
return false;
}
return tokenStream.getToken(ttp, TokenStream::TemplateTail);
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::taggedTemplate(YieldHandling yieldHandling, Node nodeList, TokenKind tt)
{
Node callSiteObjNode = handler.newCallSiteObject(pos().begin);
if (!callSiteObjNode)
return false;
handler.addList(nodeList, callSiteObjNode);
while (true) {
if (!appendToCallSiteObj(callSiteObjNode))
return false;
if (tt != TOK_TEMPLATE_HEAD)
break;
if (!addExprAndGetNextTemplStrToken(yieldHandling, nodeList, &tt))
return false;
}
handler.setEndPosition(nodeList, callSiteObjNode);
return true;
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::templateLiteral(YieldHandling yieldHandling)
{
Node pn = noSubstitutionTemplate();
if (!pn)
return null();
Node nodeList = handler.newList(PNK_TEMPLATE_STRING_LIST, pn);
if (!nodeList)
return null();
TokenKind tt;
do {
if (!addExprAndGetNextTemplStrToken(yieldHandling, nodeList, &tt))
return null();
pn = noSubstitutionTemplate();
if (!pn)
return null();
handler.addList(nodeList, pn);
} while (tt == TOK_TEMPLATE_HEAD);
return nodeList;
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::functionDefinition(uint32_t preludeStart, Node pn, InHandling inHandling,
YieldHandling yieldHandling,
HandleAtom funName, FunctionSyntaxKind kind,
GeneratorKind generatorKind, FunctionAsyncKind asyncKind,
bool tryAnnexB /* = false */)
{
MOZ_ASSERT_IF(kind == Statement, funName);
MOZ_ASSERT_IF(asyncKind == AsyncFunction, generatorKind == StarGenerator);
// When fully parsing a LazyScript, we do not fully reparse its inner
// functions, which are also lazy. Instead, their free variables and
// source extents are recorded and may be skipped.
if (handler.canSkipLazyInnerFunctions()) {
if (!skipLazyInnerFunction(pn, preludeStart, kind, tryAnnexB))
return null();
return pn;
}
RootedObject proto(context);
if (generatorKind == StarGenerator) {
// If we are off the main thread, the generator meta-objects have
// already been created by js::StartOffThreadParseScript, so cx will not
// be necessary.
JSContext* cx = context->maybeJSContext();
proto = GlobalObject::getOrCreateStarGeneratorFunctionPrototype(cx, context->global());
if (!proto)
return null();
}
RootedFunction fun(context, newFunction(funName, kind, generatorKind, asyncKind, proto));
if (!fun)
return null();
// Speculatively parse using the directives of the parent parsing context.
// If a directive is encountered (e.g., "use strict") that changes how the
// function should have been parsed, we backup and reparse with the new set
// of directives.
Directives directives(pc);
Directives newDirectives = directives;
TokenStream::Position start(keepAtoms);
tokenStream.tell(&start);
// Parse the inner function. The following is a loop as we may attempt to
// reparse a function due to failed syntax parsing and encountering new
// "use foo" directives.
while (true) {
if (trySyntaxParseInnerFunction(pn, fun, preludeStart, inHandling, yieldHandling, kind,
generatorKind, asyncKind, tryAnnexB, directives,
&newDirectives))
{
break;
}
// Return on error.
if (tokenStream.hadError() || directives == newDirectives)
return null();
// Assignment must be monotonic to prevent infinitely attempting to
// reparse.
MOZ_ASSERT_IF(directives.strict(), newDirectives.strict());
MOZ_ASSERT_IF(directives.asmJS(), newDirectives.asmJS());
directives = newDirectives;
tokenStream.seek(start);
// functionFormalParametersAndBody may have already set pn->pn_body before failing.
handler.setFunctionFormalParametersAndBody(pn, null());
}
return pn;
}
template <>
bool
Parser<FullParseHandler>::trySyntaxParseInnerFunction(ParseNode* pn, HandleFunction fun,
uint32_t preludeStart,
InHandling inHandling,
YieldHandling yieldHandling,
FunctionSyntaxKind kind,
GeneratorKind generatorKind,
FunctionAsyncKind asyncKind,
bool tryAnnexB,
Directives inheritedDirectives,
Directives* newDirectives)
{
// Try a syntax parse for this inner function.
do {
// If we're assuming this function is an IIFE, always perform a full
// parse to avoid the overhead of a lazy syntax-only parse. Although
// the prediction may be incorrect, IIFEs are common enough that it
// pays off for lots of code.
if (pn->isLikelyIIFE() && generatorKind == NotGenerator)
break;
Parser<SyntaxParseHandler>* parser = handler.syntaxParser;
if (!parser)
break;
UsedNameTracker::RewindToken token = usedNames.getRewindToken();
// Move the syntax parser to the current position in the stream.
TokenStream::Position position(keepAtoms);
tokenStream.tell(&position);
if (!parser->tokenStream.seek(position, tokenStream))
return false;
// Make a FunctionBox before we enter the syntax parser, because |pn|
// still expects a FunctionBox to be attached to it during BCE, and
// the syntax parser cannot attach one to it.
FunctionBox* funbox = newFunctionBox(pn, fun, preludeStart, inheritedDirectives,
generatorKind, asyncKind, tryAnnexB);
if (!funbox)
return false;
funbox->initWithEnclosingParseContext(pc, kind);
if (!parser->innerFunction(SyntaxParseHandler::NodeGeneric, pc, funbox, preludeStart,
inHandling, yieldHandling, kind,
inheritedDirectives, newDirectives))
{
if (parser->hadAbortedSyntaxParse()) {
// Try again with a full parse. UsedNameTracker needs to be
// rewound to just before we tried the syntax parse for
// correctness.
parser->clearAbortedSyntaxParse();
usedNames.rewind(token);
MOZ_ASSERT_IF(parser->context->isJSContext(),
!parser->context->asJSContext()->isExceptionPending());
break;
}
return false;
}
// Advance this parser over tokens processed by the syntax parser.
parser->tokenStream.tell(&position);
if (!tokenStream.seek(position, parser->tokenStream))
return false;
// Update the end position of the parse node.
pn->pn_pos.end = tokenStream.currentToken().pos.end;
return true;
} while (false);
// We failed to do a syntax parse above, so do the full parse.
return innerFunction(pn, pc, fun, preludeStart, inHandling, yieldHandling, kind,
generatorKind, asyncKind, tryAnnexB, inheritedDirectives, newDirectives);
}
template <>
bool
Parser<SyntaxParseHandler>::trySyntaxParseInnerFunction(Node pn, HandleFunction fun,
uint32_t preludeStart,
InHandling inHandling,
YieldHandling yieldHandling,
FunctionSyntaxKind kind,
GeneratorKind generatorKind,
FunctionAsyncKind asyncKind,
bool tryAnnexB,
Directives inheritedDirectives,
Directives* newDirectives)
{
// This is already a syntax parser, so just parse the inner function.
return innerFunction(pn, pc, fun, preludeStart, inHandling, yieldHandling, kind,
generatorKind, asyncKind, tryAnnexB, inheritedDirectives, newDirectives);
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::innerFunction(Node pn, ParseContext* outerpc, FunctionBox* funbox,
uint32_t preludeStart,
InHandling inHandling, YieldHandling yieldHandling,
FunctionSyntaxKind kind, Directives inheritedDirectives,
Directives* newDirectives)
{
// Note that it is possible for outerpc != this->pc, as we may be
// attempting to syntax parse an inner function from an outer full
// parser. In that case, outerpc is a ParseContext from the full parser
// instead of the current top of the stack of the syntax parser.
// Push a new ParseContext.
ParseContext funpc(this, funbox, newDirectives);
if (!funpc.init())
return false;
if (!functionFormalParametersAndBody(inHandling, yieldHandling, pn, kind))
return false;
return leaveInnerFunction(outerpc);
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::innerFunction(Node pn, ParseContext* outerpc, HandleFunction fun,
uint32_t preludeStart,
InHandling inHandling, YieldHandling yieldHandling,
FunctionSyntaxKind kind,
GeneratorKind generatorKind, FunctionAsyncKind asyncKind,
bool tryAnnexB,
Directives inheritedDirectives, Directives* newDirectives)
{
// Note that it is possible for outerpc != this->pc, as we may be
// attempting to syntax parse an inner function from an outer full
// parser. In that case, outerpc is a ParseContext from the full parser
// instead of the current top of the stack of the syntax parser.
FunctionBox* funbox = newFunctionBox(pn, fun, preludeStart, inheritedDirectives,
generatorKind, asyncKind, tryAnnexB);
if (!funbox)
return false;
funbox->initWithEnclosingParseContext(outerpc, kind);
return innerFunction(pn, outerpc, funbox, preludeStart, inHandling, yieldHandling, kind,
inheritedDirectives, newDirectives);
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::appendToCallSiteObj(Node callSiteObj)
{
Node cookedNode = noSubstitutionTemplate();
if (!cookedNode)
return false;
JSAtom* atom = tokenStream.getRawTemplateStringAtom();
if (!atom)
return false;
Node rawNode = handler.newTemplateStringLiteral(atom, pos());
if (!rawNode)
return false;
handler.addToCallSiteObject(callSiteObj, rawNode, cookedNode);
return true;
}
template <>
ParseNode*
Parser<FullParseHandler>::standaloneLazyFunction(HandleFunction fun, bool strict,
GeneratorKind generatorKind,
FunctionAsyncKind asyncKind)
{
MOZ_ASSERT(checkOptionsCalled);
Node pn = handler.newFunctionStatement();
if (!pn)
return null();
Directives directives(strict);
FunctionBox* funbox = newFunctionBox(pn, fun, /* preludeStart = */ 0, directives,
generatorKind, asyncKind, /* tryAnnexB = */ false);
if (!funbox)
return null();
funbox->initFromLazyFunction();
Directives newDirectives = directives;
ParseContext funpc(this, funbox, &newDirectives);
if (!funpc.init())
return null();
// Our tokenStream has no current token, so pn's position is garbage.
// Substitute the position of the first token in our source. If the function
// is a not-async arrow, use TokenStream::Operand to keep
// verifyConsistentModifier from complaining (we will use
// TokenStream::Operand in functionArguments).
TokenStream::Modifier modifier = (fun->isArrow() && asyncKind == SyncFunction)
? TokenStream::Operand : TokenStream::None;
if (!tokenStream.peekTokenPos(&pn->pn_pos, modifier))
return null();
YieldHandling yieldHandling = GetYieldHandling(generatorKind, asyncKind);
FunctionSyntaxKind syntaxKind = Statement;
if (fun->isClassConstructor())
syntaxKind = ClassConstructor;
else if (fun->isMethod())
syntaxKind = Method;
else if (fun->isGetter())
syntaxKind = Getter;
else if (fun->isSetter())
syntaxKind = Setter;
else if (fun->isArrow())
syntaxKind = Arrow;
if (!functionFormalParametersAndBody(InAllowed, yieldHandling, pn, syntaxKind)) {
MOZ_ASSERT(directives == newDirectives);
return null();
}
if (!FoldConstants(context, &pn, this))
return null();
return pn;
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::functionFormalParametersAndBody(InHandling inHandling,
YieldHandling yieldHandling,
Node pn, FunctionSyntaxKind kind,
Maybe<uint32_t> parameterListEnd /* = Nothing() */)
{
// Given a properly initialized parse context, try to parse an actual
// function without concern for conversion to strict mode, use of lazy
// parsing and such.
FunctionBox* funbox = pc->functionBox();
RootedFunction fun(context, funbox->function());
AutoAwaitIsKeyword awaitIsKeyword(&tokenStream, funbox->isAsync());
if (!functionArguments(yieldHandling, kind, pn))
return false;
Maybe<ParseContext::VarScope> varScope;
if (funbox->hasParameterExprs) {
varScope.emplace(this);
if (!varScope->init(pc))
return false;
} else {
pc->functionScope().useAsVarScope(pc);
}
if (kind == Arrow) {
bool matched;
if (!tokenStream.matchToken(&matched, TOK_ARROW))
return false;
if (!matched) {
error(JSMSG_BAD_ARROW_ARGS);
return false;
}
}
// When parsing something for new Function() we have to make sure to
// only treat a certain part of the source as a parameter list.
if (parameterListEnd.isSome() && parameterListEnd.value() != pos().begin) {
error(JSMSG_UNEXPECTED_PARAMLIST_END);
return false;
}
// Parse the function body.
FunctionBodyType bodyType = StatementListBody;
TokenKind tt;
if (!tokenStream.getToken(&tt, TokenStream::Operand))
return false;
if (tt != TOK_LC) {
if ((funbox->isStarGenerator() && !funbox->isAsync()) || kind == Method ||
kind == GetterNoExpressionClosure || kind == SetterNoExpressionClosure ||
IsConstructorKind(kind)) {
error(JSMSG_CURLY_BEFORE_BODY);
return false;
}
if (kind != Arrow) {
#if JS_HAS_EXPR_CLOSURES
if (!warnOnceAboutExprClosure())
return false;
#else
error(JSMSG_CURLY_BEFORE_BODY);
return false;
#endif
}
tokenStream.ungetToken();
bodyType = ExpressionBody;
#if JS_HAS_EXPR_CLOSURES
fun->setIsExprBody();
#endif
}
// Arrow function parameters inherit yieldHandling from the enclosing
// context, but the arrow body doesn't. E.g. in |(a = yield) => yield|,
// |yield| in the parameters is either a name or keyword, depending on
// whether the arrow function is enclosed in a generator function or not.
// Whereas the |yield| in the function body is always parsed as a name.
YieldHandling bodyYieldHandling = GetYieldHandling(pc->generatorKind(), pc->asyncKind());
Node body = functionBody(inHandling, bodyYieldHandling, kind, bodyType);
if (!body)
return false;
if ((kind != Method && !IsConstructorKind(kind)) && fun->explicitName()) {
RootedPropertyName propertyName(context, fun->explicitName()->asPropertyName());
if (!checkStrictBinding(propertyName, handler.getPosition(pn)))
return false;
}
if (bodyType == StatementListBody) {
bool matched;
if (!tokenStream.matchToken(&matched, TOK_RC, TokenStream::Operand))
return false;
if (!matched) {
error(JSMSG_CURLY_AFTER_BODY);
return false;
}
funbox->bufEnd = pos().end;
} else {
#if !JS_HAS_EXPR_CLOSURES
MOZ_ASSERT(kind == Arrow);
#endif
if (tokenStream.hadError())
return false;
funbox->bufEnd = pos().end;
if (kind == Statement && !MatchOrInsertSemicolonAfterExpression(tokenStream))
return false;
}
if (IsMethodDefinitionKind(kind) && pc->superScopeNeedsHomeObject())
funbox->setNeedsHomeObject();
if (!finishFunction())
return false;
handler.setEndPosition(body, pos().begin);
handler.setEndPosition(pn, pos().end);
handler.setFunctionBody(pn, body);
return true;
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::functionStmt(uint32_t preludeStart, YieldHandling yieldHandling,
DefaultHandling defaultHandling, FunctionAsyncKind asyncKind)
{
MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_FUNCTION));
// Annex B.3.4 says we can parse function declarations unbraced under if
// or else as if it were braced. That is, |if (x) function f() {}| is
// parsed as |if (x) { function f() {} }|.
Maybe<ParseContext::Statement> synthesizedStmtForAnnexB;
Maybe<ParseContext::Scope> synthesizedScopeForAnnexB;
if (!pc->sc()->strict()) {
ParseContext::Statement* stmt = pc->innermostStatement();
if (stmt && stmt->kind() == StatementKind::If) {
synthesizedStmtForAnnexB.emplace(pc, StatementKind::Block);
synthesizedScopeForAnnexB.emplace(this);
if (!synthesizedScopeForAnnexB->init(pc))
return null();
}
}
// In sloppy mode, Annex B.3.2 allows labelled function declarations.
// Otherwise it's a parse error.
ParseContext::Statement* declaredInStmt = pc->innermostStatement();
if (declaredInStmt && declaredInStmt->kind() == StatementKind::Label) {
MOZ_ASSERT(!pc->sc()->strict(),
"labeled functions shouldn't be parsed in strict mode");
// Find the innermost non-label statement. Report an error if it's
// unbraced: functions can't appear in it. Otherwise the statement
// (or its absence) determines the scope the function's bound in.
while (declaredInStmt && declaredInStmt->kind() == StatementKind::Label)
declaredInStmt = declaredInStmt->enclosing();
if (declaredInStmt && !StatementKindIsBraced(declaredInStmt->kind())) {
error(JSMSG_SLOPPY_FUNCTION_LABEL);
return null();
}
}
TokenKind tt;
if (!tokenStream.getToken(&tt))
return null();
GeneratorKind generatorKind = asyncKind == AsyncFunction ? StarGenerator : NotGenerator;
if (tt == TOK_MUL) {
if (asyncKind != SyncFunction) {
error(JSMSG_ASYNC_GENERATOR);
return null();
}
generatorKind = StarGenerator;
if (!tokenStream.getToken(&tt))
return null();
}
RootedPropertyName name(context);
if (tt == TOK_NAME || tt == TOK_YIELD) {
name = bindingIdentifier(yieldHandling);
if (!name)
return null();
} else if (defaultHandling == AllowDefaultName) {
name = context->names().starDefaultStar;
tokenStream.ungetToken();
} else {
/* Unnamed function expressions are forbidden in statement context. */
error(JSMSG_UNNAMED_FUNCTION_STMT);
return null();
}
// Note the declared name and check for early errors.
bool tryAnnexB = false;
if (declaredInStmt) {
MOZ_ASSERT(declaredInStmt->kind() != StatementKind::Label);
MOZ_ASSERT(StatementKindIsBraced(declaredInStmt->kind()));
if (!pc->sc()->strict() && generatorKind == NotGenerator) {
// In sloppy mode, try Annex B.3.3 semantics. If making an
// additional 'var' binding of the same name does not throw an
// early error, do so. This 'var' binding would be assigned
// the function object when its declaration is reached, not at
// the start of the block.
if (!tryDeclareVarForAnnexBLexicalFunction(name, &tryAnnexB))
return null();
}
if (!noteDeclaredName(name, DeclarationKind::LexicalFunction, pos()))
return null();
} else {
if (!noteDeclaredName(name, DeclarationKind::BodyLevelFunction, pos()))
return null();
// Body-level functions in modules are always closed over.
if (pc->atModuleLevel())
pc->varScope().lookupDeclaredName(name)->value()->setClosedOver();
}
Node pn = handler.newFunctionStatement();
if (!pn)
return null();
YieldHandling newYieldHandling = GetYieldHandling(generatorKind, asyncKind);
Node fun = functionDefinition(preludeStart, pn, InAllowed, newYieldHandling,
name, Statement, generatorKind, asyncKind, tryAnnexB);
if (!fun)
return null();
if (synthesizedStmtForAnnexB) {
Node synthesizedStmtList = handler.newStatementList(handler.getPosition(fun));
if (!synthesizedStmtList)
return null();
handler.addStatementToList(synthesizedStmtList, fun);
return finishLexicalScope(*synthesizedScopeForAnnexB, synthesizedStmtList);
}
return fun;
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::functionExpr(uint32_t preludeStart, InvokedPrediction invoked,
FunctionAsyncKind asyncKind)
{
MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_FUNCTION));
AutoAwaitIsKeyword awaitIsKeyword(&tokenStream, asyncKind == AsyncFunction);
GeneratorKind generatorKind = asyncKind == AsyncFunction ? StarGenerator : NotGenerator;
TokenKind tt;
if (!tokenStream.getToken(&tt))
return null();
if (tt == TOK_MUL) {
if (asyncKind != SyncFunction) {
error(JSMSG_ASYNC_GENERATOR);
return null();
}
generatorKind = StarGenerator;
if (!tokenStream.getToken(&tt))
return null();
}
YieldHandling yieldHandling = GetYieldHandling(generatorKind, asyncKind);
RootedPropertyName name(context);
if (tt == TOK_NAME || tt == TOK_YIELD) {
name = bindingIdentifier(yieldHandling);
if (!name)
return null();
} else {
tokenStream.ungetToken();
}
Node pn = handler.newFunctionExpression();
if (!pn)
return null();
if (invoked)
pn = handler.setLikelyIIFE(pn);
return functionDefinition(preludeStart, pn, InAllowed, yieldHandling, name, Expression,
generatorKind, asyncKind);
}
/*
* Return true if this node, known to be an unparenthesized string literal,
* could be the string of a directive in a Directive Prologue. Directive
* strings never contain escape sequences or line continuations.
* isEscapeFreeStringLiteral, below, checks whether the node itself could be
* a directive.
*/
static inline bool
IsEscapeFreeStringLiteral(const TokenPos& pos, JSAtom* str)
{
/*
* If the string's length in the source code is its length as a value,
* accounting for the quotes, then it must not contain any escape
* sequences or line continuations.
*/
return pos.begin + str->length() + 2 == pos.end;
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::checkUnescapedName()
{
const Token& token = tokenStream.currentToken();
if (!token.nameContainsEscape())
return true;
errorAt(token.pos.begin, JSMSG_ESCAPED_KEYWORD);
return false;
}
template <>
bool
Parser<SyntaxParseHandler>::asmJS(Node list)
{
// While asm.js could technically be validated and compiled during syntax
// parsing, we have no guarantee that some later JS wouldn't abort the
// syntax parse and cause us to re-parse (and re-compile) the asm.js module.
// For simplicity, unconditionally abort the syntax parse when "use asm" is
// encountered so that asm.js is always validated/compiled exactly once
// during a full parse.
JS_ALWAYS_FALSE(abortIfSyntaxParser());
return false;
}
template <>
bool
Parser<FullParseHandler>::asmJS(Node list)
{
// Disable syntax parsing in anything nested inside the asm.js module.
handler.disableSyntaxParser();
// We should be encountering the "use asm" directive for the first time; if
// the directive is already, we must have failed asm.js validation and we're
// reparsing. In that case, don't try to validate again. A non-null
// newDirectives means we're not in a normal function.
if (!pc->newDirectives || pc->newDirectives->asmJS())
return true;
// If there is no ScriptSource, then we are doing a non-compiling parse and
// so we shouldn't (and can't, without a ScriptSource) compile.
if (ss == nullptr)
return true;
pc->functionBox()->useAsm = true;
// Attempt to validate and compile this asm.js module. On success, the
// tokenStream has been advanced to the closing }. On failure, the
// tokenStream is in an indeterminate state and we must reparse the
// function from the beginning. Reparsing is triggered by marking that a
// new directive has been encountered and returning 'false'.
bool validated;
if (!CompileAsmJS(context, *this, list, &validated))
return false;
if (!validated) {
pc->newDirectives->setAsmJS();
return false;
}
return true;
}
/*
* Recognize Directive Prologue members and directives. Assuming |pn| is a
* candidate for membership in a directive prologue, recognize directives and
* set |pc|'s flags accordingly. If |pn| is indeed part of a prologue, set its
* |pn_prologue| flag.
*
* Note that the following is a strict mode function:
*
* function foo() {
* "blah" // inserted semi colon
* "blurgh"
* "use\x20loose"
* "use strict"
* }
*
* That is, even though "use\x20loose" can never be a directive, now or in the
* future (because of the hex escape), the Directive Prologue extends through it
* to the "use strict" statement, which is indeed a directive.
*/
template <typename ParseHandler>
bool
Parser<ParseHandler>::maybeParseDirective(Node list, Node possibleDirective, bool* cont)
{
TokenPos directivePos;
JSAtom* directive = handler.isStringExprStatement(possibleDirective, &directivePos);
*cont = !!directive;
if (!*cont)
return true;
if (IsEscapeFreeStringLiteral(directivePos, directive)) {
// Mark this statement as being a possibly legitimate part of a
// directive prologue, so the bytecode emitter won't warn about it being
// useless code. (We mustn't just omit the statement entirely yet, as it
// could be producing the value of an eval or JSScript execution.)
//
// Note that even if the string isn't one we recognize as a directive,
// the emitter still shouldn't flag it as useless, as it could become a
// directive in the future. We don't want to interfere with people
// taking advantage of directive-prologue-enabled features that appear
// in other browsers first.
handler.setInDirectivePrologue(possibleDirective);
if (directive == context->names().useStrict) {
// Functions with non-simple parameter lists (destructuring,
// default or rest parameters) must not contain a "use strict"
// directive.
if (pc->isFunctionBox()) {
FunctionBox* funbox = pc->functionBox();
if (!funbox->hasSimpleParameterList()) {
const char* parameterKind = funbox->hasDestructuringArgs
? "destructuring"
: funbox->hasParameterExprs
? "default"
: "rest";
errorAt(directivePos.begin, JSMSG_STRICT_NON_SIMPLE_PARAMS, parameterKind);
return false;
}
}
// We're going to be in strict mode. Note that this scope explicitly
// had "use strict";
pc->sc()->setExplicitUseStrict();
if (!pc->sc()->strict()) {
// We keep track of the one possible strict violation that could
// occur in the directive prologue -- octal escapes -- and
// complain now.
if (tokenStream.sawOctalEscape()) {
error(JSMSG_DEPRECATED_OCTAL);
return false;
}
pc->sc()->strictScript = true;
}
} else if (directive == context->names().useAsm) {
if (pc->isFunctionBox())
return asmJS(list);
return warningAt(directivePos.begin, JSMSG_USE_ASM_DIRECTIVE_FAIL);
}
}
return true;
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::statementList(YieldHandling yieldHandling)
{
JS_CHECK_RECURSION(context, return null());
Node pn = handler.newStatementList(pos());
if (!pn)
return null();
bool canHaveDirectives = pc->atBodyLevel();
if (canHaveDirectives)
tokenStream.clearSawOctalEscape();
bool afterReturn = false;
bool warnedAboutStatementsAfterReturn = false;
uint32_t statementBegin = 0;
for (;;) {
TokenKind tt = TOK_EOF;
if (!tokenStream.peekToken(&tt, TokenStream::Operand)) {
if (tokenStream.isEOF())
isUnexpectedEOF_ = true;
return null();
}
if (tt == TOK_EOF || tt == TOK_RC)
break;
if (afterReturn) {
if (!tokenStream.peekOffset(&statementBegin, TokenStream::Operand))
return null();
}
Node next = statementListItem(yieldHandling, canHaveDirectives);
if (!next) {
if (tokenStream.isEOF())
isUnexpectedEOF_ = true;
return null();
}
if (!warnedAboutStatementsAfterReturn) {
if (afterReturn) {
if (!handler.isStatementPermittedAfterReturnStatement(next)) {
if (!warningAt(statementBegin, JSMSG_STMT_AFTER_RETURN))
return null();
warnedAboutStatementsAfterReturn = true;
}
} else if (handler.isReturnStatement(next)) {
afterReturn = true;
}
}
if (canHaveDirectives) {
if (!maybeParseDirective(pn, next, &canHaveDirectives))
return null();
}
handler.addStatementToList(pn, next);
}
return pn;
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::condition(InHandling inHandling, YieldHandling yieldHandling)
{
MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_COND);
Node pn = exprInParens(inHandling, yieldHandling, TripledotProhibited);
if (!pn)
return null();
MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_COND);
/* Check for (a = b) and warn about possible (a == b) mistype. */
if (handler.isUnparenthesizedAssignment(pn)) {
if (!extraWarning(JSMSG_EQUAL_AS_ASSIGN))
return null();
}
return pn;
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::matchLabel(YieldHandling yieldHandling, MutableHandle<PropertyName*> label)
{
TokenKind tt = TOK_EOF;
if (!tokenStream.peekTokenSameLine(&tt, TokenStream::Operand))
return false;
if (tt == TOK_NAME || tt == TOK_YIELD) {
tokenStream.consumeKnownToken(tt, TokenStream::Operand);
label.set(labelIdentifier(yieldHandling));
if (!label)
return false;
} else {
label.set(nullptr);
}
return true;
}
template <typename ParseHandler>
Parser<ParseHandler>::PossibleError::PossibleError(Parser<ParseHandler>& parser)
: parser_(parser)
{}
template <typename ParseHandler>
typename Parser<ParseHandler>::PossibleError::Error&
Parser<ParseHandler>::PossibleError::error(ErrorKind kind)
{
if (kind == ErrorKind::Expression)
return exprError_;
MOZ_ASSERT(kind == ErrorKind::Destructuring);
return destructuringError_;
}
template <typename ParseHandler>
void
Parser<ParseHandler>::PossibleError::setResolved(ErrorKind kind)
{
error(kind).state_ = ErrorState::None;
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::PossibleError::hasError(ErrorKind kind)
{
return error(kind).state_ == ErrorState::Pending;
}
template <typename ParseHandler>
void
Parser<ParseHandler>::PossibleError::setPending(ErrorKind kind, const TokenPos& pos,
unsigned errorNumber)
{
// Don't overwrite a previously recorded error.
if (hasError(kind))
return;
// If we report an error later, we'll do it from the position where we set
// the state to pending.
Error& err = error(kind);
err.offset_ = pos.begin;
err.errorNumber_ = errorNumber;
err.state_ = ErrorState::Pending;
}
template <typename ParseHandler>
void
Parser<ParseHandler>::PossibleError::setPendingDestructuringErrorAt(const TokenPos& pos,
unsigned errorNumber)
{
setPending(ErrorKind::Destructuring, pos, errorNumber);
}
template <typename ParseHandler>
void
Parser<ParseHandler>::PossibleError::setPendingExpressionErrorAt(const TokenPos& pos,
unsigned errorNumber)
{
setPending(ErrorKind::Expression, pos, errorNumber);
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::PossibleError::checkForError(ErrorKind kind)
{
if (!hasError(kind))
return true;
Error& err = error(kind);
parser_.errorAt(err.offset_, err.errorNumber_);
return false;
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::PossibleError::checkForDestructuringError()
{
// Clear pending expression error, because we're definitely not in an
// expression context.
setResolved(ErrorKind::Expression);
// Report any pending destructuring error.
return checkForError(ErrorKind::Destructuring);
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::PossibleError::checkForExpressionError()
{
// Clear pending destructuring error, because we're definitely not in a
// destructuring context.
setResolved(ErrorKind::Destructuring);
// Report any pending expression error.
return checkForError(ErrorKind::Expression);
}
template <typename ParseHandler>
void
Parser<ParseHandler>::PossibleError::transferErrorTo(ErrorKind kind, PossibleError* other)
{
if (hasError(kind) && !other->hasError(kind)) {
Error& err = error(kind);
Error& otherErr = other->error(kind);
otherErr.offset_ = err.offset_;
otherErr.errorNumber_ = err.errorNumber_;
otherErr.state_ = err.state_;
}
}
template <typename ParseHandler>
void
Parser<ParseHandler>::PossibleError::transferErrorsTo(PossibleError* other)
{
MOZ_ASSERT(other);
MOZ_ASSERT(this != other);
MOZ_ASSERT(&parser_ == &other->parser_,
"Can't transfer fields to an instance which belongs to a different parser");
transferErrorTo(ErrorKind::Destructuring, other);
transferErrorTo(ErrorKind::Expression, other);
}
template <>
bool
Parser<FullParseHandler>::checkDestructuringName(ParseNode* expr, Maybe<DeclarationKind> maybeDecl)
{
MOZ_ASSERT(!handler.isUnparenthesizedDestructuringPattern(expr));
// Parentheses are forbidden around destructuring *patterns* (but allowed
// around names). Use our nicer error message for parenthesized, nested
// patterns.
if (handler.isParenthesizedDestructuringPattern(expr)) {
errorAt(expr->pn_pos.begin, JSMSG_BAD_DESTRUCT_PARENS);
return false;
}
// This expression might be in a variable-binding pattern where only plain,
// unparenthesized names are permitted.
if (maybeDecl) {
// Destructuring patterns in declarations must only contain
// unparenthesized names.
if (!handler.isUnparenthesizedName(expr)) {
errorAt(expr->pn_pos.begin, JSMSG_NO_VARIABLE_NAME);
return false;
}
RootedPropertyName name(context, expr->name());
return noteDeclaredName(name, *maybeDecl, expr->pn_pos);
}
// Otherwise this is an expression in destructuring outside a declaration.
if (handler.isNameAnyParentheses(expr)) {
if (const char* chars = handler.nameIsArgumentsEvalAnyParentheses(expr, context)) {
if (!strictModeErrorAt(expr->pn_pos.begin, JSMSG_BAD_STRICT_ASSIGN, chars))
return false;
}
return true;
}
if (handler.isPropertyAccess(expr))
return true;
errorAt(expr->pn_pos.begin, JSMSG_BAD_DESTRUCT_TARGET);
return false;
}
template <>
bool
Parser<FullParseHandler>::checkDestructuringPattern(ParseNode* pattern,
Maybe<DeclarationKind> maybeDecl,
PossibleError* possibleError /* = nullptr */);
template <>
bool
Parser<FullParseHandler>::checkDestructuringObject(ParseNode* objectPattern,
Maybe<DeclarationKind> maybeDecl)
{
MOZ_ASSERT(objectPattern->isKind(PNK_OBJECT));
for (ParseNode* member = objectPattern->pn_head; member; member = member->pn_next) {
ParseNode* target;
if (member->isKind(PNK_MUTATEPROTO)) {
target = member->pn_kid;
} else {
MOZ_ASSERT(member->isKind(PNK_COLON) || member->isKind(PNK_SHORTHAND));
MOZ_ASSERT_IF(member->isKind(PNK_SHORTHAND),
member->pn_left->isKind(PNK_OBJECT_PROPERTY_NAME) &&
member->pn_right->isKind(PNK_NAME) &&
member->pn_left->pn_atom == member->pn_right->pn_atom);
target = member->pn_right;
}
if (handler.isUnparenthesizedAssignment(target))
target = target->pn_left;
if (handler.isUnparenthesizedDestructuringPattern(target)) {
if (!checkDestructuringPattern(target, maybeDecl))
return false;
} else {
if (!checkDestructuringName(target, maybeDecl))
return false;
}
}
return true;
}
template <>
bool
Parser<FullParseHandler>::checkDestructuringArray(ParseNode* arrayPattern,
Maybe<DeclarationKind> maybeDecl)
{
MOZ_ASSERT(arrayPattern->isKind(PNK_ARRAY));
for (ParseNode* element = arrayPattern->pn_head; element; element = element->pn_next) {
if (element->isKind(PNK_ELISION))
continue;
ParseNode* target;
if (element->isKind(PNK_SPREAD)) {
if (element->pn_next) {
errorAt(element->pn_next->pn_pos.begin, JSMSG_PARAMETER_AFTER_REST);
return false;
}
target = element->pn_kid;
} else if (handler.isUnparenthesizedAssignment(element)) {
target = element->pn_left;
} else {
target = element;
}
if (handler.isUnparenthesizedDestructuringPattern(target)) {
if (!checkDestructuringPattern(target, maybeDecl))
return false;
} else {
if (!checkDestructuringName(target, maybeDecl))
return false;
}
}
return true;
}
/*
* Destructuring patterns can appear in two kinds of contexts:
*
* - assignment-like: assignment expressions and |for| loop heads. In
* these cases, the patterns' property value positions can be
* arbitrary lvalue expressions; the destructuring is just a fancy
* assignment.
*
* - binding-like: |var| and |let| declarations, functions' formal
* parameter lists, |catch| clauses, and comprehension tails. In
* these cases, the patterns' property value positions must be
* simple names; the destructuring defines them as new variables.
*
* In both cases, other code parses the pattern as an arbitrary
* primaryExpr, and then, here in checkDestructuringPattern, verify
* that the tree is a valid AssignmentPattern or BindingPattern.
*
* In assignment-like contexts, we parse the pattern with
* pc->inDestructuringDecl clear, so the lvalue expressions in the
* pattern are parsed normally. primaryExpr links variable references
* into the appropriate use chains; creates placeholder definitions;
* and so on. checkDestructuringPattern won't bind any new names and
* we specialize lvalues as appropriate.
*
* In declaration-like contexts, the normal variable reference
* processing would just be an obstruction, because we're going to
* define the names that appear in the property value positions as new
* variables anyway. In this case, we parse the pattern with
* pc->inDestructuringDecl set, which directs primaryExpr to leave
* whatever name nodes it creates unconnected. Then, here in
* checkDestructuringPattern, we require the pattern's property value
* positions to be simple names, and define them as appropriate to the
* context.
*/
template <>
bool
Parser<FullParseHandler>::checkDestructuringPattern(ParseNode* pattern,
Maybe<DeclarationKind> maybeDecl,
PossibleError* possibleError /* = nullptr */)
{
if (pattern->isKind(PNK_ARRAYCOMP)) {
errorAt(pattern->pn_pos.begin, JSMSG_ARRAY_COMP_LEFTSIDE);
return false;
}
bool isDestructuring = pattern->isKind(PNK_ARRAY)
? checkDestructuringArray(pattern, maybeDecl)
: checkDestructuringObject(pattern, maybeDecl);
// Report any pending destructuring error.
if (isDestructuring && possibleError && !possibleError->checkForDestructuringError())
return false;
return isDestructuring;
}
template <>
bool
Parser<SyntaxParseHandler>::checkDestructuringPattern(Node pattern,
Maybe<DeclarationKind> maybeDecl,
PossibleError* possibleError /* = nullptr */)
{
return abortIfSyntaxParser();
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::destructuringDeclaration(DeclarationKind kind, YieldHandling yieldHandling,
TokenKind tt)
{
MOZ_ASSERT(tokenStream.isCurrentTokenType(tt));
MOZ_ASSERT(tt == TOK_LB || tt == TOK_LC);
PossibleError possibleError(*this);
Node pattern;
{
pc->inDestructuringDecl = Some(kind);
pattern = primaryExpr(yieldHandling, TripledotProhibited, tt, &possibleError);
pc->inDestructuringDecl = Nothing();
}
if (!pattern || !checkDestructuringPattern(pattern, Some(kind), &possibleError))
return null();
return pattern;
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::destructuringDeclarationWithoutYieldOrAwait(DeclarationKind kind,
YieldHandling yieldHandling,
TokenKind tt)
{
uint32_t startYieldOffset = pc->lastYieldOffset;
uint32_t startAwaitOffset = pc->lastAwaitOffset;
Node res = destructuringDeclaration(kind, yieldHandling, tt);
if (res) {
if (pc->lastYieldOffset != startYieldOffset) {
errorAt(pc->lastYieldOffset, JSMSG_YIELD_IN_DEFAULT);
return null();
}
if (pc->lastAwaitOffset != startAwaitOffset) {
errorAt(pc->lastAwaitOffset, JSMSG_AWAIT_IN_DEFAULT);
return null();
}
}
return res;
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::blockStatement(YieldHandling yieldHandling, unsigned errorNumber)
{
MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_LC));
ParseContext::Statement stmt(pc, StatementKind::Block);
ParseContext::Scope scope(this);
if (!scope.init(pc))
return null();
Node list = statementList(yieldHandling);
if (!list)
return null();
MUST_MATCH_TOKEN_MOD(TOK_RC, TokenStream::Operand, errorNumber);
return finishLexicalScope(scope, list);
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::expressionAfterForInOrOf(ParseNodeKind forHeadKind,
YieldHandling yieldHandling)
{
MOZ_ASSERT(forHeadKind == PNK_FORIN || forHeadKind == PNK_FOROF);
Node pn = forHeadKind == PNK_FOROF
? assignExpr(InAllowed, yieldHandling, TripledotProhibited)
: expr(InAllowed, yieldHandling, TripledotProhibited);
return pn;
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::declarationPattern(Node decl, DeclarationKind declKind, TokenKind tt,
bool initialDeclaration, YieldHandling yieldHandling,
ParseNodeKind* forHeadKind, Node* forInOrOfExpression)
{
MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_LB) ||
tokenStream.isCurrentTokenType(TOK_LC));
Node pattern = destructuringDeclaration(declKind, yieldHandling, tt);
if (!pattern)
return null();
if (initialDeclaration && forHeadKind) {
bool isForIn, isForOf;
if (!matchInOrOf(&isForIn, &isForOf))
return null();
if (isForIn) {
*forHeadKind = PNK_FORIN;
} else if (isForOf) {
*forHeadKind = PNK_FOROF;
// Annex B.3.5 has different early errors for vars in for-of loops.
if (declKind == DeclarationKind::Var)
declKind = DeclarationKind::ForOfVar;
} else {
*forHeadKind = PNK_FORHEAD;
}
if (*forHeadKind != PNK_FORHEAD) {
*forInOrOfExpression = expressionAfterForInOrOf(*forHeadKind, yieldHandling);
if (!*forInOrOfExpression)
return null();
return pattern;
}
}
MUST_MATCH_TOKEN(TOK_ASSIGN, JSMSG_BAD_DESTRUCT_DECL);
Node init = assignExpr(forHeadKind ? InProhibited : InAllowed,
yieldHandling, TripledotProhibited);
if (!init)
return null();
handler.checkAndSetIsDirectRHSAnonFunction(init);
if (forHeadKind) {
// For for(;;) declarations, consistency with |for (;| parsing requires
// that the ';' first be examined as Operand, even though absence of a
// binary operator (examined with modifier None) terminated |init|.
// For all other declarations, through ASI's infinite majesty, a next
// token on a new line would begin an expression.
tokenStream.addModifierException(TokenStream::OperandIsNone);
}
return handler.newBinary(PNK_ASSIGN, pattern, init);
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::initializerInNameDeclaration(Node decl, Node binding,
Handle<PropertyName*> name,
DeclarationKind declKind,
bool initialDeclaration,
YieldHandling yieldHandling,
ParseNodeKind* forHeadKind,
Node* forInOrOfExpression)
{
MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_ASSIGN));
uint32_t initializerOffset;
if (!tokenStream.peekOffset(&initializerOffset, TokenStream::Operand))
return false;
Node initializer = assignExpr(forHeadKind ? InProhibited : InAllowed,
yieldHandling, TripledotProhibited);
if (!initializer)
return false;
handler.checkAndSetIsDirectRHSAnonFunction(initializer);
if (forHeadKind) {
if (initialDeclaration) {
bool isForIn, isForOf;
if (!matchInOrOf(&isForIn, &isForOf))
return false;
// An initialized declaration can't appear in a for-of:
//
// for (var/let/const x = ... of ...); // BAD
if (isForOf) {
errorAt(initializerOffset, JSMSG_OF_AFTER_FOR_LOOP_DECL);
return false;
}
if (isForIn) {
// Lexical declarations in for-in loops can't be initialized:
//
// for (let/const x = ... in ...); // BAD
if (DeclarationKindIsLexical(declKind)) {
errorAt(initializerOffset, JSMSG_IN_AFTER_LEXICAL_FOR_DECL);
return false;
}
// This leaves only initialized for-in |var| declarations. ES6
// forbids these; later ES un-forbids in non-strict mode code.
*forHeadKind = PNK_FORIN;
if (!strictModeErrorAt(initializerOffset, JSMSG_INVALID_FOR_IN_DECL_WITH_INIT))
return false;
*forInOrOfExpression = expressionAfterForInOrOf(PNK_FORIN, yieldHandling);
if (!*forInOrOfExpression)
return false;
} else {
*forHeadKind = PNK_FORHEAD;
}
} else {
MOZ_ASSERT(*forHeadKind == PNK_FORHEAD);
// In the very rare case of Parser::assignExpr consuming an
// ArrowFunction with block body, when full-parsing with the arrow
// function being a skipped lazy inner function, we don't have
// lookahead for the next token. Do a one-off peek here to be
// consistent with what Parser::matchForInOrOf does in the other
// arm of this |if|.
//
// If you think this all sounds pretty code-smelly, you're almost
// certainly correct.
TokenKind ignored;
if (!tokenStream.peekToken(&ignored))
return false;
}
if (*forHeadKind == PNK_FORHEAD) {
// Per Parser::forHeadStart, the semicolon in |for (;| is
// ultimately gotten as Operand. But initializer expressions
// terminate with the absence of an operator gotten as None,
// so we need an exception.
tokenStream.addModifierException(TokenStream::OperandIsNone);
}
}
return handler.finishInitializerAssignment(binding, initializer);
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::declarationName(Node decl, DeclarationKind declKind, TokenKind tt,
bool initialDeclaration, YieldHandling yieldHandling,
ParseNodeKind* forHeadKind, Node* forInOrOfExpression)
{
// Anything other than TOK_YIELD or TOK_NAME is an error.
if (tt != TOK_NAME && tt != TOK_YIELD) {
error(JSMSG_NO_VARIABLE_NAME);
return null();
}
RootedPropertyName name(context, bindingIdentifier(yieldHandling));
if (!name)
return null();
Node binding = newName(name);
if (!binding)
return null();
TokenPos namePos = pos();
// The '=' context after a variable name in a declaration is an opportunity
// for ASI, and thus for the next token to start an ExpressionStatement:
//
// var foo // VariableDeclaration
// /bar/g; // ExpressionStatement
//
// Therefore get the token here as Operand.
bool matched;
if (!tokenStream.matchToken(&matched, TOK_ASSIGN, TokenStream::Operand))
return null();
if (matched) {
if (!initializerInNameDeclaration(decl, binding, name, declKind, initialDeclaration,
yieldHandling, forHeadKind, forInOrOfExpression))
{
return null();
}
} else {
tokenStream.addModifierException(TokenStream::NoneIsOperand);
if (initialDeclaration && forHeadKind) {
bool isForIn, isForOf;
if (!matchInOrOf(&isForIn, &isForOf))
return null();
if (isForIn) {
*forHeadKind = PNK_FORIN;
} else if (isForOf) {
*forHeadKind = PNK_FOROF;
// Annex B.3.5 has different early errors for vars in for-of loops.
if (declKind == DeclarationKind::Var)
declKind = DeclarationKind::ForOfVar;
} else {
*forHeadKind = PNK_FORHEAD;
}
}
if (forHeadKind && *forHeadKind != PNK_FORHEAD) {
*forInOrOfExpression = expressionAfterForInOrOf(*forHeadKind, yieldHandling);
if (!*forInOrOfExpression)
return null();
} else {
// Normal const declarations, and const declarations in for(;;)
// heads, must be initialized.
if (declKind == DeclarationKind::Const) {
errorAt(namePos.begin, JSMSG_BAD_CONST_DECL);
return null();
}
}
}
// Note the declared name after knowing whether or not we are in a for-of
// loop, due to special early error semantics in Annex B.3.5.
if (!noteDeclaredName(name, declKind, namePos))
return null();
return binding;
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::declarationList(YieldHandling yieldHandling,
ParseNodeKind kind,
ParseNodeKind* forHeadKind /* = nullptr */,
Node* forInOrOfExpression /* = nullptr */)
{
MOZ_ASSERT(kind == PNK_VAR || kind == PNK_LET || kind == PNK_CONST);
JSOp op;
DeclarationKind declKind;
switch (kind) {
case PNK_VAR:
op = JSOP_DEFVAR;
declKind = DeclarationKind::Var;
break;
case PNK_CONST:
op = JSOP_DEFCONST;
declKind = DeclarationKind::Const;
break;
case PNK_LET:
op = JSOP_DEFLET;
declKind = DeclarationKind::Let;
break;
default:
MOZ_CRASH("Unknown declaration kind");
}
Node decl = handler.newDeclarationList(kind, op);
if (!decl)
return null();
bool matched;
bool initialDeclaration = true;
do {
MOZ_ASSERT_IF(!initialDeclaration && forHeadKind,
*forHeadKind == PNK_FORHEAD);
TokenKind tt;
if (!tokenStream.getToken(&tt))
return null();
Node binding = (tt == TOK_LB || tt == TOK_LC)
? declarationPattern(decl, declKind, tt, initialDeclaration, yieldHandling,
forHeadKind, forInOrOfExpression)
: declarationName(decl, declKind, tt, initialDeclaration, yieldHandling,
forHeadKind, forInOrOfExpression);
if (!binding)
return null();
handler.addList(decl, binding);
if (forHeadKind && *forHeadKind != PNK_FORHEAD)
break;
initialDeclaration = false;
if (!tokenStream.matchToken(&matched, TOK_COMMA))
return null();
} while (matched);
return decl;
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::lexicalDeclaration(YieldHandling yieldHandling, bool isConst)
{
/*
* Parse body-level lets without a new block object. ES6 specs
* that an execution environment's initial lexical environment
* is the VariableEnvironment, i.e., body-level lets are in
* the same environment record as vars.
*
* However, they cannot be parsed exactly as vars, as ES6
* requires that uninitialized lets throw ReferenceError on use.
*
* See 8.1.1.1.6 and the note in 13.2.1.
*/
Node decl = declarationList(yieldHandling, isConst ? PNK_CONST : PNK_LET);
if (!decl || !MatchOrInsertSemicolonAfterExpression(tokenStream))
return null();
return decl;
}
template <>
bool
Parser<FullParseHandler>::namedImportsOrNamespaceImport(TokenKind tt, Node importSpecSet)
{
if (tt == TOK_LC) {
TokenStream::Modifier modifier = TokenStream::KeywordIsName;
while (true) {
// Handle the forms |import {} from 'a'| and
// |import { ..., } from 'a'| (where ... is non empty), by
// escaping the loop early if the next token is }.
if (!tokenStream.peekToken(&tt, TokenStream::KeywordIsName))
return false;
if (tt == TOK_RC)
break;
// If the next token is a keyword, the previous call to
// peekToken matched it as a TOK_NAME, and put it in the
// lookahead buffer, so this call will match keywords as well.
MUST_MATCH_TOKEN_MOD(TOK_NAME, TokenStream::KeywordIsName, JSMSG_NO_IMPORT_NAME);
Rooted<PropertyName*> importName(context, tokenStream.currentName());
TokenPos importNamePos = pos();
TokenKind maybeAs;
if (!tokenStream.peekToken(&maybeAs))
return null();
if (maybeAs == TOK_NAME &&
tokenStream.nextName() == context->names().as)
{
tokenStream.consumeKnownToken(TOK_NAME);
if (!checkUnescapedName())
return false;
TokenKind afterAs;
if (!tokenStream.getToken(&afterAs))
return false;
if (afterAs != TOK_NAME && afterAs != TOK_YIELD) {
error(JSMSG_NO_BINDING_NAME);
return false;
}
} else {
// Keywords cannot be bound to themselves, so an import name
// that is a keyword is a syntax error if it is not followed
// by the keyword 'as'.
// See the ImportSpecifier production in ES6 section 15.2.2.
if (IsKeyword(importName)) {
JSAutoByteString bytes;
if (!AtomToPrintableString(context, importName, &bytes))
return false;
error(JSMSG_AS_AFTER_RESERVED_WORD, bytes.ptr());
return false;
}
}
RootedPropertyName bindingAtom(context, importedBinding());
if (!bindingAtom)
return false;
Node bindingName = newName(bindingAtom);
if (!bindingName)
return false;
if (!noteDeclaredName(bindingAtom, DeclarationKind::Import, pos()))
return false;
Node importNameNode = newName(importName, importNamePos);
if (!importNameNode)
return false;
Node importSpec = handler.newBinary(PNK_IMPORT_SPEC, importNameNode, bindingName);
if (!importSpec)
return false;
handler.addList(importSpecSet, importSpec);
bool matched;
if (!tokenStream.matchToken(&matched, TOK_COMMA))
return false;
if (!matched) {
modifier = TokenStream::None;
break;
}
}
MUST_MATCH_TOKEN_MOD(TOK_RC, modifier, JSMSG_RC_AFTER_IMPORT_SPEC_LIST);
} else {
MOZ_ASSERT(tt == TOK_MUL);
if (!tokenStream.getToken(&tt))
return false;
if (tt != TOK_NAME || tokenStream.currentName() != context->names().as) {
error(JSMSG_AS_AFTER_IMPORT_STAR);
return false;
}
if (!checkUnescapedName())
return false;
MUST_MATCH_TOKEN(TOK_NAME, JSMSG_NO_BINDING_NAME);
Node importName = newName(context->names().star);
if (!importName)
return false;
// Namespace imports are are not indirect bindings but lexical
// definitions that hold a module namespace object. They are treated
// as const variables which are initialized during the
// ModuleDeclarationInstantiation step.
RootedPropertyName bindingName(context, importedBinding());
if (!bindingName)
return false;
Node bindingNameNode = newName(bindingName);
if (!bindingNameNode)
return false;
if (!noteDeclaredName(bindingName, DeclarationKind::Const, pos()))
return false;
// The namespace import name is currently required to live on the
// environment.
pc->varScope().lookupDeclaredName(bindingName)->value()->setClosedOver();
Node importSpec = handler.newBinary(PNK_IMPORT_SPEC, importName, bindingNameNode);
if (!importSpec)
return false;
handler.addList(importSpecSet, importSpec);
}
return true;
}
template<>
bool
Parser<SyntaxParseHandler>::namedImportsOrNamespaceImport(TokenKind tt, Node importSpecSet)
{
MOZ_ALWAYS_FALSE(abortIfSyntaxParser());
return false;
}
template<>
ParseNode*
Parser<FullParseHandler>::importDeclaration()
{
MOZ_ASSERT(tokenStream.currentToken().type == TOK_IMPORT);
if (!pc->atModuleLevel()) {
error(JSMSG_IMPORT_DECL_AT_TOP_LEVEL);
return null();
}
uint32_t begin = pos().begin;
TokenKind tt;
if (!tokenStream.getToken(&tt))
return null();
Node importSpecSet = handler.newList(PNK_IMPORT_SPEC_LIST);
if (!importSpecSet)
return null();
if (tt == TOK_NAME || tt == TOK_LC || tt == TOK_MUL) {
if (tt == TOK_NAME) {
// Handle the form |import a from 'b'|, by adding a single import
// specifier to the list, with 'default' as the import name and
// 'a' as the binding name. This is equivalent to
// |import { default as a } from 'b'|.
Node importName = newName(context->names().default_);
if (!importName)
return null();
RootedPropertyName bindingAtom(context, importedBinding());
if (!bindingAtom)
return null();
Node bindingName = newName(bindingAtom);
if (!bindingName)
return null();
if (!noteDeclaredName(bindingAtom, DeclarationKind::Import, pos()))
return null();
Node importSpec = handler.newBinary(PNK_IMPORT_SPEC, importName, bindingName);
if (!importSpec)
return null();
handler.addList(importSpecSet, importSpec);
if (!tokenStream.peekToken(&tt))
return null();
if (tt == TOK_COMMA) {
tokenStream.consumeKnownToken(tt);
if (!tokenStream.getToken(&tt))
return null();
if (tt != TOK_LC && tt != TOK_MUL) {
error(JSMSG_NAMED_IMPORTS_OR_NAMESPACE_IMPORT);
return null();
}
if (!namedImportsOrNamespaceImport(tt, importSpecSet))
return null();
}
} else {
if (!namedImportsOrNamespaceImport(tt, importSpecSet))
return null();
}
if (!tokenStream.getToken(&tt))
return null();
if (tt != TOK_NAME || tokenStream.currentName() != context->names().from) {
error(JSMSG_FROM_AFTER_IMPORT_CLAUSE);
return null();
}
if (!checkUnescapedName())
return null();
MUST_MATCH_TOKEN(TOK_STRING, JSMSG_MODULE_SPEC_AFTER_FROM);
} else if (tt == TOK_STRING) {
// Handle the form |import 'a'| by leaving the list empty. This is
// equivalent to |import {} from 'a'|.
importSpecSet->pn_pos.end = importSpecSet->pn_pos.begin;
} else {
error(JSMSG_DECLARATION_AFTER_IMPORT);
return null();
}
Node moduleSpec = stringLiteral();
if (!moduleSpec)
return null();
if (!MatchOrInsertSemicolonAfterNonExpression(tokenStream))
return null();
ParseNode* node =
handler.newImportDeclaration(importSpecSet, moduleSpec, TokenPos(begin, pos().end));
if (!node || !pc->sc()->asModuleContext()->builder.processImport(node))
return null();
return node;
}
template<>
SyntaxParseHandler::Node
Parser<SyntaxParseHandler>::importDeclaration()
{
JS_ALWAYS_FALSE(abortIfSyntaxParser());
return SyntaxParseHandler::NodeFailure;
}
template<>
bool
Parser<FullParseHandler>::checkExportedName(JSAtom* exportName)
{
if (!pc->sc()->asModuleContext()->builder.hasExportedName(exportName))
return true;
JSAutoByteString str;
if (!AtomToPrintableString(context, exportName, &str))
return false;
error(JSMSG_DUPLICATE_EXPORT_NAME, str.ptr());
return false;
}
template<>
bool
Parser<SyntaxParseHandler>::checkExportedName(JSAtom* exportName)
{
MOZ_ALWAYS_FALSE(abortIfSyntaxParser());
return false;
}
template<>
bool
Parser<FullParseHandler>::checkExportedNamesForDeclaration(ParseNode* node)
{
MOZ_ASSERT(node->isArity(PN_LIST));
for (ParseNode* binding = node->pn_head; binding; binding = binding->pn_next) {
if (binding->isKind(PNK_ASSIGN))
binding = binding->pn_left;
MOZ_ASSERT(binding->isKind(PNK_NAME));
if (!checkExportedName(binding->pn_atom))
return false;
}
return true;
}
template<>
bool
Parser<SyntaxParseHandler>::checkExportedNamesForDeclaration(Node node)
{
MOZ_ALWAYS_FALSE(abortIfSyntaxParser());
return false;
}
template<>
ParseNode*
Parser<FullParseHandler>::exportDeclaration()
{
MOZ_ASSERT(tokenStream.currentToken().type == TOK_EXPORT);
if (!pc->atModuleLevel()) {
error(JSMSG_EXPORT_DECL_AT_TOP_LEVEL);
return null();
}
uint32_t begin = pos().begin;
Node kid;
TokenKind tt;
if (!tokenStream.getToken(&tt))
return null();
switch (tt) {
case TOK_LC: {
kid = handler.newList(PNK_EXPORT_SPEC_LIST);
if (!kid)
return null();
while (true) {
// Handle the forms |export {}| and |export { ..., }| (where ...
// is non empty), by escaping the loop early if the next token
// is }.
if (!tokenStream.peekToken(&tt))
return null();
if (tt == TOK_RC)
break;
MUST_MATCH_TOKEN(TOK_NAME, JSMSG_NO_BINDING_NAME);
Node bindingName = newName(tokenStream.currentName());
if (!bindingName)
return null();
bool foundAs;
if (!tokenStream.matchContextualKeyword(&foundAs, context->names().as))
return null();
if (foundAs)
MUST_MATCH_TOKEN_MOD(TOK_NAME, TokenStream::KeywordIsName, JSMSG_NO_EXPORT_NAME);
Node exportName = newName(tokenStream.currentName());
if (!exportName)
return null();
if (!checkExportedName(exportName->pn_atom))
return null();
Node exportSpec = handler.newBinary(PNK_EXPORT_SPEC, bindingName, exportName);
if (!exportSpec)
return null();
handler.addList(kid, exportSpec);
bool matched;
if (!tokenStream.matchToken(&matched, TOK_COMMA))
return null();
if (!matched)
break;
}
MUST_MATCH_TOKEN(TOK_RC, JSMSG_RC_AFTER_EXPORT_SPEC_LIST);
// Careful! If |from| follows, even on a new line, it must start a
// FromClause:
//
// export { x }
// from "foo"; // a single ExportDeclaration
//
// But if it doesn't, we might have an ASI opportunity in Operand
// context, so simply matching a contextual keyword won't work:
//
// export { x } // ExportDeclaration, terminated by ASI
// fro\u006D // ExpressionStatement, the name "from"
//
// In that case let MatchOrInsertSemicolonAfterNonExpression sort out
// ASI or any necessary error.
TokenKind tt;
if (!tokenStream.getToken(&tt, TokenStream::Operand))
return null();
if (tt == TOK_NAME &&
tokenStream.currentToken().name() == context->names().from &&
!tokenStream.currentToken().nameContainsEscape())
{
MUST_MATCH_TOKEN(TOK_STRING, JSMSG_MODULE_SPEC_AFTER_FROM);
Node moduleSpec = stringLiteral();
if (!moduleSpec)
return null();
if (!MatchOrInsertSemicolonAfterNonExpression(tokenStream))
return null();
ParseNode* node = handler.newExportFromDeclaration(begin, kid, moduleSpec);
if (!node || !pc->sc()->asModuleContext()->builder.processExportFrom(node))
return null();
return node;
}
tokenStream.ungetToken();
if (!MatchOrInsertSemicolonAfterNonExpression(tokenStream))
return null();
break;
}
case TOK_MUL: {
kid = handler.newList(PNK_EXPORT_SPEC_LIST);
if (!kid)
return null();
// Handle the form |export *| by adding a special export batch
// specifier to the list.
Node exportSpec = handler.newNullary(PNK_EXPORT_BATCH_SPEC, JSOP_NOP, pos());
if (!exportSpec)
return null();
handler.addList(kid, exportSpec);
if (!tokenStream.getToken(&tt))
return null();
if (tt != TOK_NAME || tokenStream.currentName() != context->names().from) {
error(JSMSG_FROM_AFTER_EXPORT_STAR);
return null();
}
if (!checkUnescapedName())
return null();
MUST_MATCH_TOKEN(TOK_STRING, JSMSG_MODULE_SPEC_AFTER_FROM);
Node moduleSpec = stringLiteral();
if (!moduleSpec)
return null();
if (!MatchOrInsertSemicolonAfterNonExpression(tokenStream))
return null();
ParseNode* node = handler.newExportFromDeclaration(begin, kid, moduleSpec);
if (!node || !pc->sc()->asModuleContext()->builder.processExportFrom(node))
return null();
return node;
}
case TOK_FUNCTION:
kid = functionStmt(pos().begin, YieldIsKeyword, NameRequired);
if (!kid)
return null();
if (!checkExportedName(kid->pn_funbox->function()->explicitName()))
return null();
break;
case TOK_CLASS: {
kid = classDefinition(YieldIsKeyword, ClassStatement, NameRequired);
if (!kid)
return null();
const ClassNode& cls = kid->as<ClassNode>();
MOZ_ASSERT(cls.names());
if (!checkExportedName(cls.names()->innerBinding()->pn_atom))
return null();
break;
}
case TOK_VAR:
kid = declarationList(YieldIsName, PNK_VAR);
if (!kid)
return null();
if (!MatchOrInsertSemicolonAfterExpression(tokenStream))
return null();
if (!checkExportedNamesForDeclaration(kid))
return null();
break;
case TOK_DEFAULT: {
if (!tokenStream.getToken(&tt, TokenStream::Operand))
return null();
if (!checkExportedName(context->names().default_))
return null();
ParseNode* nameNode = nullptr;
switch (tt) {
case TOK_FUNCTION:
kid = functionStmt(pos().begin, YieldIsKeyword, AllowDefaultName);
if (!kid)
return null();
break;
case TOK_CLASS:
kid = classDefinition(YieldIsKeyword, ClassStatement, AllowDefaultName);
if (!kid)
return null();
break;
default: {
if (tt == TOK_NAME && tokenStream.currentName() == context->names().async) {
TokenKind nextSameLine = TOK_EOF;
if (!tokenStream.peekTokenSameLine(&nextSameLine))
return null();
if (nextSameLine == TOK_FUNCTION) {
tokenStream.consumeKnownToken(nextSameLine);
kid = functionStmt(pos().begin, YieldIsName, AllowDefaultName, AsyncFunction);
if (!kid)
return null();
break;
}
}
tokenStream.ungetToken();
RootedPropertyName name(context, context->names().starDefaultStar);
nameNode = newName(name);
if (!nameNode)
return null();
if (!noteDeclaredName(name, DeclarationKind::Const, pos()))
return null();
kid = assignExpr(InAllowed, YieldIsKeyword, TripledotProhibited);
if (!kid)
return null();
if (!MatchOrInsertSemicolonAfterExpression(tokenStream))
return null();
break;
}
}
ParseNode* node = handler.newExportDefaultDeclaration(kid, nameNode,
TokenPos(begin, pos().end));
if (!node || !pc->sc()->asModuleContext()->builder.processExport(node))
return null();
return node;
}
case TOK_CONST:
kid = lexicalDeclaration(YieldIsName, /* isConst = */ true);
if (!kid)
return null();
if (!checkExportedNamesForDeclaration(kid))
return null();
break;
case TOK_NAME:
if (tokenStream.currentName() == context->names().let) {
if (!checkUnescapedName())
return null();
kid = lexicalDeclaration(YieldIsName, /* isConst = */ false);
if (!kid)
return null();
if (!checkExportedNamesForDeclaration(kid))
return null();
break;
}
MOZ_FALLTHROUGH;
default:
error(JSMSG_DECLARATION_AFTER_EXPORT);
return null();
}
ParseNode* node = handler.newExportDeclaration(kid, TokenPos(begin, pos().end));
if (!node || !pc->sc()->asModuleContext()->builder.processExport(node))
return null();
return node;
}
template<>
SyntaxParseHandler::Node
Parser<SyntaxParseHandler>::exportDeclaration()
{
JS_ALWAYS_FALSE(abortIfSyntaxParser());
return SyntaxParseHandler::NodeFailure;
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::expressionStatement(YieldHandling yieldHandling, InvokedPrediction invoked)
{
tokenStream.ungetToken();
Node pnexpr = expr(InAllowed, yieldHandling, TripledotProhibited,
/* possibleError = */ nullptr, invoked);
if (!pnexpr)
return null();
if (!MatchOrInsertSemicolonAfterExpression(tokenStream))
return null();
return handler.newExprStatement(pnexpr, pos().end);
}
template <class ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::consequentOrAlternative(YieldHandling yieldHandling)
{
TokenKind next;
if (!tokenStream.peekToken(&next, TokenStream::Operand))
return null();
if (next == TOK_FUNCTION) {
// Apply Annex B.3.4 in non-strict code to allow FunctionDeclaration as
// the consequent/alternative of an |if| or |else|. Parser::statement
// will report the strict mode error.
if (!pc->sc()->strict()) {
tokenStream.consumeKnownToken(next, TokenStream::Operand);
return functionStmt(pos().begin, yieldHandling, NameRequired);
}
}
return statement(yieldHandling);
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::ifStatement(YieldHandling yieldHandling)
{
Vector<Node, 4> condList(context), thenList(context);
Vector<uint32_t, 4> posList(context);
Node elseBranch;
ParseContext::Statement stmt(pc, StatementKind::If);
while (true) {
uint32_t begin = pos().begin;
/* An IF node has three kids: condition, then, and optional else. */
Node cond = condition(InAllowed, yieldHandling);
if (!cond)
return null();
TokenKind tt;
if (!tokenStream.peekToken(&tt, TokenStream::Operand))
return null();
if (tt == TOK_SEMI) {
if (!extraWarning(JSMSG_EMPTY_CONSEQUENT))
return null();
}
Node thenBranch = consequentOrAlternative(yieldHandling);
if (!thenBranch)
return null();
if (!condList.append(cond) || !thenList.append(thenBranch) || !posList.append(begin))
return null();
bool matched;
if (!tokenStream.matchToken(&matched, TOK_ELSE, TokenStream::Operand))
return null();
if (matched) {
if (!tokenStream.matchToken(&matched, TOK_IF, TokenStream::Operand))
return null();
if (matched)
continue;
elseBranch = consequentOrAlternative(yieldHandling);
if (!elseBranch)
return null();
} else {
elseBranch = null();
}
break;
}
for (int i = condList.length() - 1; i >= 0; i--) {
elseBranch = handler.newIfStatement(posList[i], condList[i], thenList[i], elseBranch);
if (!elseBranch)
return null();
}
return elseBranch;
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::doWhileStatement(YieldHandling yieldHandling)
{
uint32_t begin = pos().begin;
ParseContext::Statement stmt(pc, StatementKind::DoLoop);
Node body = statement(yieldHandling);
if (!body)
return null();
MUST_MATCH_TOKEN_MOD(TOK_WHILE, TokenStream::Operand, JSMSG_WHILE_AFTER_DO);
Node cond = condition(InAllowed, yieldHandling);
if (!cond)
return null();
// The semicolon after do-while is even more optional than most
// semicolons in JS. Web compat required this by 2004:
// http://bugzilla.mozilla.org/show_bug.cgi?id=238945
// ES3 and ES5 disagreed, but ES6 conforms to Web reality:
// https://bugs.ecmascript.org/show_bug.cgi?id=157
// To parse |do {} while (true) false| correctly, use Operand.
bool ignored;
if (!tokenStream.matchToken(&ignored, TOK_SEMI, TokenStream::Operand))
return null();
return handler.newDoWhileStatement(body, cond, TokenPos(begin, pos().end));
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::whileStatement(YieldHandling yieldHandling)
{
uint32_t begin = pos().begin;
ParseContext::Statement stmt(pc, StatementKind::WhileLoop);
Node cond = condition(InAllowed, yieldHandling);
if (!cond)
return null();
Node body = statement(yieldHandling);
if (!body)
return null();
return handler.newWhileStatement(begin, cond, body);
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::matchInOrOf(bool* isForInp, bool* isForOfp)
{
TokenKind tt;
if (!tokenStream.getToken(&tt))
return false;
*isForInp = tt == TOK_IN;
*isForOfp = tt == TOK_NAME && tokenStream.currentToken().name() == context->names().of;
if (!*isForInp && !*isForOfp) {
tokenStream.ungetToken();
} else {
if (tt == TOK_NAME && !checkUnescapedName())
return false;
}
MOZ_ASSERT_IF(*isForInp || *isForOfp, *isForInp != *isForOfp);
return true;
}
template <class ParseHandler>
bool
Parser<ParseHandler>::forHeadStart(YieldHandling yieldHandling,
ParseNodeKind* forHeadKind,
Node* forInitialPart,
Maybe<ParseContext::Scope>& forLoopLexicalScope,
Node* forInOrOfExpression)
{
MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_LP));
TokenKind tt;
if (!tokenStream.peekToken(&tt, TokenStream::Operand))
return null();
// Super-duper easy case: |for (;| is a C-style for-loop with no init
// component.
if (tt == TOK_SEMI) {
*forInitialPart = null();
*forHeadKind = PNK_FORHEAD;
return true;
}
// Parsing after |for (var| is also relatively simple (from this method's
// point of view). No block-related work complicates matters, so delegate
// to Parser::declaration.
if (tt == TOK_VAR) {
tokenStream.consumeKnownToken(tt, TokenStream::Operand);
// Pass null for block object because |var| declarations don't use one.
*forInitialPart = declarationList(yieldHandling, PNK_VAR, forHeadKind,
forInOrOfExpression);
return *forInitialPart != null();
}
// Otherwise we have a lexical declaration or an expression.
// For-in loop backwards compatibility requires that |let| starting a
// for-loop that's not a (new to ES6) for-of loop, in non-strict mode code,
// parse as an identifier. (|let| in for-of is always a declaration.)
bool parsingLexicalDeclaration = false;
bool letIsIdentifier = false;
if (tt == TOK_CONST) {
parsingLexicalDeclaration = true;
tokenStream.consumeKnownToken(tt, TokenStream::Operand);
} else if (tt == TOK_NAME &&
tokenStream.nextName() == context->names().let &&
!tokenStream.nextNameContainsEscape())
{
// We could have a {For,Lexical}Declaration, or we could have a
// LeftHandSideExpression with lookahead restrictions so it's not
// ambiguous with the former. Check for a continuation of the former
// to decide which we have.
tokenStream.consumeKnownToken(TOK_NAME, TokenStream::Operand);
TokenKind next;
if (!tokenStream.peekToken(&next))
return false;
parsingLexicalDeclaration = nextTokenContinuesLetDeclaration(next, yieldHandling);
if (!parsingLexicalDeclaration) {
tokenStream.ungetToken();
letIsIdentifier = true;
}
}
if (parsingLexicalDeclaration) {
forLoopLexicalScope.emplace(this);
if (!forLoopLexicalScope->init(pc))
return null();
// Push a temporary ForLoopLexicalHead Statement that allows for
// lexical declarations, as they are usually allowed only in braced
// statements.
ParseContext::Statement forHeadStmt(pc, StatementKind::ForLoopLexicalHead);
*forInitialPart = declarationList(yieldHandling, tt == TOK_CONST ? PNK_CONST : PNK_LET,
forHeadKind, forInOrOfExpression);
return *forInitialPart != null();
}
uint32_t exprOffset;
if (!tokenStream.peekOffset(&exprOffset, TokenStream::Operand))
return false;
// Finally, handle for-loops that start with expressions. Pass
// |InProhibited| so that |in| isn't parsed in a RelationalExpression as a
// binary operator. |in| makes it a for-in loop, *not* an |in| expression.
PossibleError possibleError(*this);
*forInitialPart = expr(InProhibited, yieldHandling, TripledotProhibited, &possibleError);
if (!*forInitialPart)
return false;
bool isForIn, isForOf;
if (!matchInOrOf(&isForIn, &isForOf))
return false;
// If we don't encounter 'in'/'of', we have a for(;;) loop. We've handled
// the init expression; the caller handles the rest. Allow the Operand
// modifier when regetting: Operand must be used to examine the ';' in
// |for (;|, and our caller handles this case and that.
if (!isForIn && !isForOf) {
if (!possibleError.checkForExpressionError())
return false;
*forHeadKind = PNK_FORHEAD;
tokenStream.addModifierException(TokenStream::OperandIsNone);
return true;
}
MOZ_ASSERT(isForIn != isForOf);
// In a for-of loop, 'let' that starts the loop head is a |let| keyword,
// per the [lookahead ≠ let] restriction on the LeftHandSideExpression
// variant of such loops. Expressions that start with |let| can't be used
// here.
//
// var let = {};
// for (let.prop of [1]) // BAD
// break;
//
// See ES6 13.7.
if (isForOf && letIsIdentifier) {
errorAt(exprOffset, JSMSG_LET_STARTING_FOROF_LHS);
return false;
}
*forHeadKind = isForIn ? PNK_FORIN : PNK_FOROF;
// Verify the left-hand side expression doesn't have a forbidden form.
if (handler.isUnparenthesizedDestructuringPattern(*forInitialPart)) {
if (!checkDestructuringPattern(*forInitialPart, Nothing(), &possibleError))
return false;
} else if (handler.isNameAnyParentheses(*forInitialPart)) {
const char* chars = handler.nameIsArgumentsEvalAnyParentheses(*forInitialPart, context);
if (chars) {
// |chars| is "arguments" or "eval" here.
if (!strictModeErrorAt(exprOffset, JSMSG_BAD_STRICT_ASSIGN, chars))
return false;
}
handler.adjustGetToSet(*forInitialPart);
} else if (handler.isPropertyAccess(*forInitialPart)) {
// Permitted: no additional testing/fixup needed.
} else if (handler.isFunctionCall(*forInitialPart)) {
if (!strictModeErrorAt(exprOffset, JSMSG_BAD_FOR_LEFTSIDE))
return false;
} else {
errorAt(exprOffset, JSMSG_BAD_FOR_LEFTSIDE);
return false;
}
if (!possibleError.checkForExpressionError())
return false;
// Finally, parse the iterated expression, making the for-loop's closing
// ')' the next token.
*forInOrOfExpression = expressionAfterForInOrOf(*forHeadKind, yieldHandling);
return *forInOrOfExpression != null();
}
template <class ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::forStatement(YieldHandling yieldHandling)
{
MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_FOR));
uint32_t begin = pos().begin;
ParseContext::Statement stmt(pc, StatementKind::ForLoop);
bool isForEach = false;
unsigned iflags = 0;
if (allowsForEachIn()) {
bool matched;
if (!tokenStream.matchContextualKeyword(&matched, context->names().each))
return null();
if (matched) {
iflags = JSITER_FOREACH;
isForEach = true;
if (!warnOnceAboutForEach())
return null();
}
}
MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_AFTER_FOR);
// PNK_FORHEAD, PNK_FORIN, or PNK_FOROF depending on the loop type.
ParseNodeKind headKind;
// |x| in either |for (x; ...; ...)| or |for (x in/of ...)|.
Node startNode;
// The next two variables are used to implement `for (let/const ...)`.
//
// We generate an implicit block, wrapping the whole loop, to store loop
// variables declared this way. Note that if the loop uses `for (var...)`
// instead, those variables go on some existing enclosing scope, so no
// implicit block scope is created.
//
// Both variables remain null/none if the loop is any other form.
// The static block scope for the implicit block scope.
Maybe<ParseContext::Scope> forLoopLexicalScope;
// The expression being iterated over, for for-in/of loops only. Unused
// for for(;;) loops.
Node iteratedExpr;
// Parse the entirety of the loop-head for a for-in/of loop (so the next
// token is the closing ')'):
//
// for (... in/of ...) ...
// ^next token
//
// ...OR, parse up to the first ';' in a C-style for-loop:
//
// for (...; ...; ...) ...
// ^next token
//
// In either case the subsequent token can be consistently accessed using
// TokenStream::None semantics.
if (!forHeadStart(yieldHandling, &headKind, &startNode, forLoopLexicalScope,
&iteratedExpr))
{
return null();
}
MOZ_ASSERT(headKind == PNK_FORIN || headKind == PNK_FOROF || headKind == PNK_FORHEAD);
Node forHead;
if (headKind == PNK_FORHEAD) {
Node init = startNode;
if (isForEach) {
errorAt(begin, JSMSG_BAD_FOR_EACH_LOOP);
return null();
}
// Look for an operand: |for (;| means we might have already examined
// this semicolon with that modifier.
MUST_MATCH_TOKEN_MOD(TOK_SEMI, TokenStream::Operand, JSMSG_SEMI_AFTER_FOR_INIT);
TokenKind tt;
if (!tokenStream.peekToken(&tt, TokenStream::Operand))
return null();
Node test;
TokenStream::Modifier mod;
if (tt == TOK_SEMI) {
test = null();
mod = TokenStream::Operand;
} else {
test = expr(InAllowed, yieldHandling, TripledotProhibited);
if (!test)
return null();
mod = TokenStream::None;
}
MUST_MATCH_TOKEN_MOD(TOK_SEMI, mod, JSMSG_SEMI_AFTER_FOR_COND);
if (!tokenStream.peekToken(&tt, TokenStream::Operand))
return null();
Node update;
if (tt == TOK_RP) {
update = null();
mod = TokenStream::Operand;
} else {
update = expr(InAllowed, yieldHandling, TripledotProhibited);
if (!update)
return null();
mod = TokenStream::None;
}
MUST_MATCH_TOKEN_MOD(TOK_RP, mod, JSMSG_PAREN_AFTER_FOR_CTRL);
TokenPos headPos(begin, pos().end);
forHead = handler.newForHead(init, test, update, headPos);
if (!forHead)
return null();
} else {
MOZ_ASSERT(headKind == PNK_FORIN || headKind == PNK_FOROF);
// |target| is the LeftHandSideExpression or declaration to which the
// per-iteration value (an arbitrary value exposed by the iteration
// protocol, or a string naming a property) is assigned.
Node target = startNode;
// Parse the rest of the for-in/of head.
if (headKind == PNK_FORIN) {
stmt.refineForKind(StatementKind::ForInLoop);
iflags |= JSITER_ENUMERATE;
} else {
if (isForEach) {
errorAt(begin, JSMSG_BAD_FOR_EACH_LOOP);
return null();
}
stmt.refineForKind(StatementKind::ForOfLoop);
}
// Parser::declaration consumed everything up to the closing ')'. That
// token follows an {Assignment,}Expression, so the next token must be
// consumed as if an operator continued the expression, i.e. as None.
MUST_MATCH_TOKEN_MOD(TOK_RP, TokenStream::None, JSMSG_PAREN_AFTER_FOR_CTRL);
TokenPos headPos(begin, pos().end);
forHead = handler.newForInOrOfHead(headKind, target, iteratedExpr, headPos);
if (!forHead)
return null();
}
Node body = statement(yieldHandling);
if (!body)
return null();
Node forLoop = handler.newForStatement(begin, forHead, body, iflags);
if (!forLoop)
return null();
if (forLoopLexicalScope)
return finishLexicalScope(*forLoopLexicalScope, forLoop);
return forLoop;
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::switchStatement(YieldHandling yieldHandling)
{
MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_SWITCH));
uint32_t begin = pos().begin;
MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_SWITCH);
Node discriminant = exprInParens(InAllowed, yieldHandling, TripledotProhibited);
if (!discriminant)
return null();
MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_SWITCH);
MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_SWITCH);
ParseContext::Statement stmt(pc, StatementKind::Switch);
ParseContext::Scope scope(this);
if (!scope.init(pc))
return null();
Node caseList = handler.newStatementList(pos());
if (!caseList)
return null();
bool seenDefault = false;
TokenKind tt;
while (true) {
if (!tokenStream.getToken(&tt, TokenStream::Operand))
return null();
if (tt == TOK_RC)
break;
uint32_t caseBegin = pos().begin;
Node caseExpr;
switch (tt) {
case TOK_DEFAULT:
if (seenDefault) {
error(JSMSG_TOO_MANY_DEFAULTS);
return null();
}
seenDefault = true;
caseExpr = null(); // The default case has pn_left == nullptr.
break;
case TOK_CASE:
caseExpr = expr(InAllowed, yieldHandling, TripledotProhibited);
if (!caseExpr)
return null();
break;
default:
error(JSMSG_BAD_SWITCH);
return null();
}
MUST_MATCH_TOKEN(TOK_COLON, JSMSG_COLON_AFTER_CASE);
Node body = handler.newStatementList(pos());
if (!body)
return null();
bool afterReturn = false;
bool warnedAboutStatementsAfterReturn = false;
uint32_t statementBegin = 0;
while (true) {
if (!tokenStream.peekToken(&tt, TokenStream::Operand))
return null();
if (tt == TOK_RC || tt == TOK_CASE || tt == TOK_DEFAULT)
break;
if (afterReturn) {
if (!tokenStream.peekOffset(&statementBegin, TokenStream::Operand))
return null();
}
Node stmt = statementListItem(yieldHandling);
if (!stmt)
return null();
if (!warnedAboutStatementsAfterReturn) {
if (afterReturn) {
if (!handler.isStatementPermittedAfterReturnStatement(stmt)) {
if (!warningAt(statementBegin, JSMSG_STMT_AFTER_RETURN))
return null();
warnedAboutStatementsAfterReturn = true;
}
} else if (handler.isReturnStatement(stmt)) {
afterReturn = true;
}
}
handler.addStatementToList(body, stmt);
}
Node casepn = handler.newCaseOrDefault(caseBegin, caseExpr, body);
if (!casepn)
return null();
handler.addCaseStatementToList(caseList, casepn);
}
caseList = finishLexicalScope(scope, caseList);
if (!caseList)
return null();
handler.setEndPosition(caseList, pos().end);
return handler.newSwitchStatement(begin, discriminant, caseList);
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::continueStatement(YieldHandling yieldHandling)
{
MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_CONTINUE));
uint32_t begin = pos().begin;
RootedPropertyName label(context);
if (!matchLabel(yieldHandling, &label))
return null();
// Labeled 'continue' statements target the nearest labeled loop
// statements with the same label. Unlabeled 'continue' statements target
// the innermost loop statement.
auto isLoop = [](ParseContext::Statement* stmt) {
return StatementKindIsLoop(stmt->kind());
};
if (label) {
ParseContext::Statement* stmt = pc->innermostStatement();
bool foundLoop = false;
for (;;) {
stmt = ParseContext::Statement::findNearest(stmt, isLoop);
if (!stmt) {
if (foundLoop)
error(JSMSG_LABEL_NOT_FOUND);
else
errorAt(begin, JSMSG_BAD_CONTINUE);
return null();
}
foundLoop = true;
// Is it labeled by our label?
bool foundTarget = false;
stmt = stmt->enclosing();
while (stmt && stmt->is<ParseContext::LabelStatement>()) {
if (stmt->as<ParseContext::LabelStatement>().label() == label) {
foundTarget = true;
break;
}
stmt = stmt->enclosing();
}
if (foundTarget)
break;
}
} else if (!pc->findInnermostStatement(isLoop)) {
error(JSMSG_BAD_CONTINUE);
return null();
}
if (!MatchOrInsertSemicolonAfterNonExpression(tokenStream))
return null();
return handler.newContinueStatement(label, TokenPos(begin, pos().end));
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::breakStatement(YieldHandling yieldHandling)
{
MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_BREAK));
uint32_t begin = pos().begin;
RootedPropertyName label(context);
if (!matchLabel(yieldHandling, &label))
return null();
// Labeled 'break' statements target the nearest labeled statements (could
// be any kind) with the same label. Unlabeled 'break' statements target
// the innermost loop or switch statement.
if (label) {
auto hasSameLabel = [&label](ParseContext::LabelStatement* stmt) {
return stmt->label() == label;
};
if (!pc->findInnermostStatement<ParseContext::LabelStatement>(hasSameLabel)) {
error(JSMSG_LABEL_NOT_FOUND);
return null();
}
} else {
auto isBreakTarget = [](ParseContext::Statement* stmt) {
return StatementKindIsUnlabeledBreakTarget(stmt->kind());
};
if (!pc->findInnermostStatement(isBreakTarget)) {
errorAt(begin, JSMSG_TOUGH_BREAK);
return null();
}
}
if (!MatchOrInsertSemicolonAfterNonExpression(tokenStream))
return null();
return handler.newBreakStatement(label, TokenPos(begin, pos().end));
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::returnStatement(YieldHandling yieldHandling)
{
MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_RETURN));
uint32_t begin = pos().begin;
MOZ_ASSERT(pc->isFunctionBox());
pc->functionBox()->usesReturn = true;
// Parse an optional operand.
//
// This is ugly, but we don't want to require a semicolon.
Node exprNode;
TokenKind tt = TOK_EOF;
if (!tokenStream.peekTokenSameLine(&tt, TokenStream::Operand))
return null();
switch (tt) {
case TOK_EOL:
case TOK_EOF:
case TOK_SEMI:
case TOK_RC:
exprNode = null();
pc->funHasReturnVoid = true;
break;
default: {
exprNode = expr(InAllowed, yieldHandling, TripledotProhibited);
if (!exprNode)
return null();
pc->funHasReturnExpr = true;
}
}
if (exprNode) {
if (!MatchOrInsertSemicolonAfterExpression(tokenStream))
return null();
} else {
if (!MatchOrInsertSemicolonAfterNonExpression(tokenStream))
return null();
}
Node pn = handler.newReturnStatement(exprNode, TokenPos(begin, pos().end));
if (!pn)
return null();
/* Disallow "return v;" in legacy generators. */
if (pc->isLegacyGenerator() && exprNode) {
errorAt(begin, JSMSG_BAD_GENERATOR_RETURN);
return null();
}
return pn;
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::newYieldExpression(uint32_t begin, typename ParseHandler::Node expr,
bool isYieldStar)
{
Node generator = newDotGeneratorName();
if (!generator)
return null();
if (isYieldStar)
return handler.newYieldStarExpression(begin, expr, generator);
return handler.newYieldExpression(begin, expr, generator);
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::newAwaitExpression(uint32_t begin, typename ParseHandler::Node expr)
{
Node generator = newDotGeneratorName();
if (!generator)
return null();
return handler.newAwaitExpression(begin, expr, generator);
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::yieldExpression(InHandling inHandling)
{
MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_YIELD));
uint32_t begin = pos().begin;
switch (pc->generatorKind()) {
case StarGenerator:
{
MOZ_ASSERT(pc->isFunctionBox());
pc->lastYieldOffset = begin;
Node exprNode;
ParseNodeKind kind = PNK_YIELD;
TokenKind tt = TOK_EOF;
if (!tokenStream.peekTokenSameLine(&tt, TokenStream::Operand))
return null();
switch (tt) {
// TOK_EOL is special; it implements the [no LineTerminator here]
// quirk in the grammar.
case TOK_EOL:
// The rest of these make up the complete set of tokens that can
// appear after any of the places where AssignmentExpression is used
// throughout the grammar. Conveniently, none of them can also be the
// start an expression.
case TOK_EOF:
case TOK_SEMI:
case TOK_RC:
case TOK_RB:
case TOK_RP:
case TOK_COLON:
case TOK_COMMA:
case TOK_IN:
// No value.
exprNode = null();
tokenStream.addModifierException(TokenStream::NoneIsOperand);
break;
case TOK_MUL:
kind = PNK_YIELD_STAR;
tokenStream.consumeKnownToken(TOK_MUL, TokenStream::Operand);
MOZ_FALLTHROUGH;
default:
exprNode = assignExpr(inHandling, YieldIsKeyword, TripledotProhibited);
if (!exprNode)
return null();
}
return newYieldExpression(begin, exprNode, kind == PNK_YIELD_STAR);
}
case NotGenerator:
// We are in code that has not seen a yield, but we are in JS 1.7 or
// later. Try to transition to being a legacy generator.
MOZ_ASSERT(tokenStream.versionNumber() >= JSVERSION_1_7);
MOZ_ASSERT(pc->lastYieldOffset == ParseContext::NoYieldOffset);
if (!abortIfSyntaxParser())
return null();
if (!pc->isFunctionBox()) {
error(JSMSG_BAD_RETURN_OR_YIELD, js_yield_str);
return null();
}
if (pc->functionBox()->isArrow()) {
errorAt(begin, JSMSG_YIELD_IN_ARROW, js_yield_str);
return null();
}
if (pc->functionBox()->function()->isMethod() ||
pc->functionBox()->function()->isGetter() ||
pc->functionBox()->function()->isSetter())
{
errorAt(begin, JSMSG_YIELD_IN_METHOD, js_yield_str);
return null();
}
if (pc->funHasReturnExpr
#if JS_HAS_EXPR_CLOSURES
|| pc->functionBox()->function()->isExprBody()
#endif
)
{
/* As in Python (see PEP-255), disallow return v; in generators. */
errorAt(begin, JSMSG_BAD_GENERATOR_RETURN);
return null();
}
pc->functionBox()->setGeneratorKind(LegacyGenerator);
MOZ_FALLTHROUGH;
case LegacyGenerator:
{
// We are in a legacy generator: a function that has already seen a
// yield.
MOZ_ASSERT(pc->isFunctionBox());
pc->lastYieldOffset = begin;
// Legacy generators do not require a value.
Node exprNode;
TokenKind tt = TOK_EOF;
if (!tokenStream.peekTokenSameLine(&tt, TokenStream::Operand))
return null();
switch (tt) {
case TOK_EOF:
case TOK_EOL:
case TOK_SEMI:
case TOK_RC:
case TOK_RB:
case TOK_RP:
case TOK_COLON:
case TOK_COMMA:
// No value.
exprNode = null();
tokenStream.addModifierException(TokenStream::NoneIsOperand);
break;
default:
exprNode = assignExpr(inHandling, YieldIsKeyword, TripledotProhibited);
if (!exprNode)
return null();
}
return newYieldExpression(begin, exprNode);
}
}
MOZ_CRASH("yieldExpr");
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::withStatement(YieldHandling yieldHandling)
{
MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_WITH));
uint32_t begin = pos().begin;
// Usually we want the constructs forbidden in strict mode code to be a
// subset of those that ContextOptions::extraWarnings() warns about, and we
// use strictModeError directly. But while 'with' is forbidden in strict
// mode code, it doesn't even merit a warning in non-strict code. See
// https://bugzilla.mozilla.org/show_bug.cgi?id=514576#c1.
if (pc->sc()->strict()) {
if (!strictModeError(JSMSG_STRICT_CODE_WITH))
return null();
}
MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_WITH);
Node objectExpr = exprInParens(InAllowed, yieldHandling, TripledotProhibited);
if (!objectExpr)
return null();
MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_WITH);
Node innerBlock;
{
ParseContext::Statement stmt(pc, StatementKind::With);
innerBlock = statement(yieldHandling);
if (!innerBlock)
return null();
}
pc->sc()->setBindingsAccessedDynamically();
return handler.newWithStatement(begin, objectExpr, innerBlock);
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::labeledItem(YieldHandling yieldHandling)
{
TokenKind tt;
if (!tokenStream.getToken(&tt, TokenStream::Operand))
return null();
if (tt == TOK_FUNCTION) {
TokenKind next;
if (!tokenStream.peekToken(&next))
return null();
// GeneratorDeclaration is only matched by HoistableDeclaration in
// StatementListItem, so generators can't be inside labels.
if (next == TOK_MUL) {
error(JSMSG_GENERATOR_LABEL);
return null();
}
// Per 13.13.1 it's a syntax error if LabelledItem: FunctionDeclaration
// is ever matched. Per Annex B.3.2 that modifies this text, this
// applies only to strict mode code.
if (pc->sc()->strict()) {
error(JSMSG_FUNCTION_LABEL);
return null();
}
return functionStmt(pos().begin, yieldHandling, NameRequired);
}
tokenStream.ungetToken();
return statement(yieldHandling);
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::labeledStatement(YieldHandling yieldHandling)
{
RootedPropertyName label(context, labelIdentifier(yieldHandling));
if (!label)
return null();
auto hasSameLabel = [&label](ParseContext::LabelStatement* stmt) {
return stmt->label() == label;
};
uint32_t begin = pos().begin;
if (pc->findInnermostStatement<ParseContext::LabelStatement>(hasSameLabel)) {
errorAt(begin, JSMSG_DUPLICATE_LABEL);
return null();
}
tokenStream.consumeKnownToken(TOK_COLON);
/* Push a label struct and parse the statement. */
ParseContext::LabelStatement stmt(pc, label);
Node pn = labeledItem(yieldHandling);
if (!pn)
return null();
return handler.newLabeledStatement(label, pn, begin);
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::throwStatement(YieldHandling yieldHandling)
{
MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_THROW));
uint32_t begin = pos().begin;
/* ECMA-262 Edition 3 says 'throw [no LineTerminator here] Expr'. */
TokenKind tt = TOK_EOF;
if (!tokenStream.peekTokenSameLine(&tt, TokenStream::Operand))
return null();
if (tt == TOK_EOF || tt == TOK_SEMI || tt == TOK_RC) {
error(JSMSG_MISSING_EXPR_AFTER_THROW);
return null();
}
if (tt == TOK_EOL) {
error(JSMSG_LINE_BREAK_AFTER_THROW);
return null();
}
Node throwExpr = expr(InAllowed, yieldHandling, TripledotProhibited);
if (!throwExpr)
return null();
if (!MatchOrInsertSemicolonAfterExpression(tokenStream))
return null();
return handler.newThrowStatement(throwExpr, TokenPos(begin, pos().end));
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::tryStatement(YieldHandling yieldHandling)
{
MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_TRY));
uint32_t begin = pos().begin;
/*
* try nodes are ternary.
* kid1 is the try statement
* kid2 is the catch node list or null
* kid3 is the finally statement
*
* catch nodes are ternary.
* kid1 is the lvalue (TOK_NAME, TOK_LB, or TOK_LC)
* kid2 is the catch guard or null if no guard
* kid3 is the catch block
*
* catch lvalue nodes are either:
* TOK_NAME for a single identifier
* TOK_RB or TOK_RC for a destructuring left-hand side
*
* finally nodes are TOK_LC statement lists.
*/
Node innerBlock;
{
MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_TRY);
ParseContext::Statement stmt(pc, StatementKind::Try);
ParseContext::Scope scope(this);
if (!scope.init(pc))
return null();
innerBlock = statementList(yieldHandling);
if (!innerBlock)
return null();
innerBlock = finishLexicalScope(scope, innerBlock);
if (!innerBlock)
return null();
MUST_MATCH_TOKEN_MOD(TOK_RC, TokenStream::Operand, JSMSG_CURLY_AFTER_TRY);
}
bool hasUnconditionalCatch = false;
Node catchList = null();
TokenKind tt;
if (!tokenStream.getToken(&tt))
return null();
if (tt == TOK_CATCH) {
catchList = handler.newCatchList();
if (!catchList)
return null();
do {
Node pnblock;
/* Check for another catch after unconditional catch. */
if (hasUnconditionalCatch) {
error(JSMSG_CATCH_AFTER_GENERAL);
return null();
}
/*
* Create a lexical scope node around the whole catch clause,
* including the head.
*/
ParseContext::Statement stmt(pc, StatementKind::Catch);
ParseContext::Scope scope(this);
if (!scope.init(pc))
return null();
/*
* Legal catch forms are:
* catch (lhs)
* catch (lhs if <boolean_expression>)
* where lhs is a name or a destructuring left-hand side.
* (the latter is legal only #ifdef JS_HAS_CATCH_GUARD)
*/
MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_CATCH);
if (!tokenStream.getToken(&tt))
return null();
Node catchName;
switch (tt) {
case TOK_LB:
case TOK_LC:
catchName = destructuringDeclaration(DeclarationKind::CatchParameter,
yieldHandling, tt);
if (!catchName)
return null();
break;
case TOK_NAME:
case TOK_YIELD: {
RootedPropertyName param(context, bindingIdentifier(yieldHandling));
if (!param)
return null();
catchName = newName(param);
if (!catchName)
return null();
if (!noteDeclaredName(param, DeclarationKind::SimpleCatchParameter, pos()))
return null();
break;
}
default:
error(JSMSG_CATCH_IDENTIFIER);
return null();
}
Node catchGuard = null();
#if JS_HAS_CATCH_GUARD
/*
* We use 'catch (x if x === 5)' (not 'catch (x : x === 5)')
* to avoid conflicting with the JS2/ECMAv4 type annotation
* catchguard syntax.
*/
bool matched;
if (!tokenStream.matchToken(&matched, TOK_IF))
return null();
if (matched) {
catchGuard = expr(InAllowed, yieldHandling, TripledotProhibited);
if (!catchGuard)
return null();
}
#endif
MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_CATCH);
MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_CATCH);
Node catchBody = catchBlockStatement(yieldHandling, scope);
if (!catchBody)
return null();
if (!catchGuard)
hasUnconditionalCatch = true;
pnblock = finishLexicalScope(scope, catchBody);
if (!pnblock)
return null();
if (!handler.addCatchBlock(catchList, pnblock, catchName, catchGuard, catchBody))
return null();
handler.setEndPosition(catchList, pos().end);
handler.setEndPosition(pnblock, pos().end);
if (!tokenStream.getToken(&tt, TokenStream::Operand))
return null();
} while (tt == TOK_CATCH);
}
Node finallyBlock = null();
if (tt == TOK_FINALLY) {
MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_FINALLY);
ParseContext::Statement stmt(pc, StatementKind::Finally);
ParseContext::Scope scope(this);
if (!scope.init(pc))
return null();
finallyBlock = statementList(yieldHandling);
if (!finallyBlock)
return null();
finallyBlock = finishLexicalScope(scope, finallyBlock);
if (!finallyBlock)
return null();
MUST_MATCH_TOKEN_MOD(TOK_RC, TokenStream::Operand, JSMSG_CURLY_AFTER_FINALLY);
} else {
tokenStream.ungetToken();
}
if (!catchList && !finallyBlock) {
error(JSMSG_CATCH_OR_FINALLY);
return null();
}
return handler.newTryStatement(begin, innerBlock, catchList, finallyBlock);
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::catchBlockStatement(YieldHandling yieldHandling,
ParseContext::Scope& catchParamScope)
{
ParseContext::Statement stmt(pc, StatementKind::Block);
// ES 13.15.7 CatchClauseEvaluation
//
// Step 8 means that the body of a catch block always has an additional
// lexical scope.
ParseContext::Scope scope(this);
if (!scope.init(pc))
return null();
// The catch parameter names cannot be redeclared inside the catch
// block, so declare the name in the inner scope.
if (!scope.addCatchParameters(pc, catchParamScope))
return null();
Node list = statementList(yieldHandling);
if (!list)
return null();
MUST_MATCH_TOKEN_MOD(TOK_RC, TokenStream::Operand, JSMSG_CURLY_AFTER_CATCH);
// The catch parameter names are not bound in the body scope, so remove
// them before generating bindings.
scope.removeCatchParameters(pc, catchParamScope);
return finishLexicalScope(scope, list);
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::debuggerStatement()
{
TokenPos p;
p.begin = pos().begin;
if (!MatchOrInsertSemicolonAfterNonExpression(tokenStream))
return null();
p.end = pos().end;
pc->sc()->setBindingsAccessedDynamically();
pc->sc()->setHasDebuggerStatement();
return handler.newDebuggerStatement(p);
}
static JSOp
JSOpFromPropertyType(PropertyType propType)
{
switch (propType) {
case PropertyType::Getter:
case PropertyType::GetterNoExpressionClosure:
return JSOP_INITPROP_GETTER;
case PropertyType::Setter:
case PropertyType::SetterNoExpressionClosure:
return JSOP_INITPROP_SETTER;
case PropertyType::Normal:
case PropertyType::Method:
case PropertyType::GeneratorMethod:
case PropertyType::AsyncMethod:
case PropertyType::Constructor:
case PropertyType::DerivedConstructor:
return JSOP_INITPROP;
default:
MOZ_CRASH("unexpected property type");
}
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::classDefinition(YieldHandling yieldHandling,
ClassContext classContext,
DefaultHandling defaultHandling)
{
MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_CLASS));
bool savedStrictness = setLocalStrictMode(true);
TokenKind tt;
if (!tokenStream.getToken(&tt))
return null();
RootedPropertyName name(context);
if (tt == TOK_NAME || tt == TOK_YIELD) {
name = bindingIdentifier(yieldHandling);
if (!name)
return null();
} else if (classContext == ClassStatement) {
if (defaultHandling == AllowDefaultName) {
name = context->names().starDefaultStar;
tokenStream.ungetToken();
} else {
// Class statements must have a bound name
error(JSMSG_UNNAMED_CLASS_STMT);
return null();
}
} else {
// Make sure to put it back, whatever it was
tokenStream.ungetToken();
}
RootedAtom propAtom(context);
// A named class creates a new lexical scope with a const binding of the
// class name.
Maybe<ParseContext::Statement> classStmt;
Maybe<ParseContext::Scope> classScope;
if (name) {
classStmt.emplace(pc, StatementKind::Block);
classScope.emplace(this);
if (!classScope->init(pc))
return null();
}
// Because the binding definitions keep track of their blockId, we need to
// create at least the inner binding later. Keep track of the name's position
// in order to provide it for the nodes created later.
TokenPos namePos = pos();
Node classHeritage = null();
bool hasHeritage;
if (!tokenStream.matchToken(&hasHeritage, TOK_EXTENDS))
return null();
if (hasHeritage) {
if (!tokenStream.getToken(&tt))
return null();
classHeritage = memberExpr(yieldHandling, TripledotProhibited, tt);
if (!classHeritage)
return null();
}
MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_CLASS);
Node classMethods = handler.newClassMethodList(pos().begin);
if (!classMethods)
return null();
bool seenConstructor = false;
for (;;) {
TokenKind tt;
if (!tokenStream.getToken(&tt, TokenStream::KeywordIsName))
return null();
if (tt == TOK_RC)
break;
if (tt == TOK_SEMI)
continue;
bool isStatic = false;
if (tt == TOK_NAME && tokenStream.currentName() == context->names().static_) {
if (!tokenStream.peekToken(&tt, TokenStream::KeywordIsName))
return null();
if (tt == TOK_RC) {
tokenStream.consumeKnownToken(tt, TokenStream::KeywordIsName);
error(JSMSG_UNEXPECTED_TOKEN, "property name", TokenKindToDesc(tt));
return null();
}
if (tt != TOK_LP) {
if (!checkUnescapedName())
return null();
isStatic = true;
} else {
tokenStream.addModifierException(TokenStream::NoneIsKeywordIsName);
tokenStream.ungetToken();
}
} else {
tokenStream.ungetToken();
}
uint32_t nameOffset;
if (!tokenStream.peekOffset(&nameOffset))
return null();
PropertyType propType;
Node propName = propertyName(yieldHandling, classMethods, &propType, &propAtom);
if (!propName)
return null();
if (propType != PropertyType::Getter && propType != PropertyType::Setter &&
propType != PropertyType::Method && propType != PropertyType::GeneratorMethod &&
propType != PropertyType::AsyncMethod &&
propType != PropertyType::Constructor && propType != PropertyType::DerivedConstructor)
{
errorAt(nameOffset, JSMSG_BAD_METHOD_DEF);
return null();
}
if (propType == PropertyType::Getter)
propType = PropertyType::GetterNoExpressionClosure;
if (propType == PropertyType::Setter)
propType = PropertyType::SetterNoExpressionClosure;
if (!isStatic && propAtom == context->names().constructor) {
if (propType != PropertyType::Method) {
errorAt(nameOffset, JSMSG_BAD_METHOD_DEF);
return null();
}
if (seenConstructor) {
errorAt(nameOffset, JSMSG_DUPLICATE_PROPERTY, "constructor");
return null();
}
seenConstructor = true;
propType = hasHeritage ? PropertyType::DerivedConstructor : PropertyType::Constructor;
} else if (isStatic && propAtom == context->names().prototype) {
errorAt(nameOffset, JSMSG_BAD_METHOD_DEF);
return null();
}
RootedAtom funName(context);
switch (propType) {
case PropertyType::GetterNoExpressionClosure:
case PropertyType::SetterNoExpressionClosure:
if (!tokenStream.isCurrentTokenType(TOK_RB)) {
funName = prefixAccessorName(propType, propAtom);
if (!funName)
return null();
}
break;
case PropertyType::Constructor:
case PropertyType::DerivedConstructor:
funName = name;
break;
default:
if (!tokenStream.isCurrentTokenType(TOK_RB))
funName = propAtom;
}
Node fn = methodDefinition(nameOffset, propType, funName);
if (!fn)
return null();
handler.checkAndSetIsDirectRHSAnonFunction(fn);
JSOp op = JSOpFromPropertyType(propType);
if (!handler.addClassMethodDefinition(classMethods, propName, fn, op, isStatic))
return null();
}
Node nameNode = null();
Node methodsOrBlock = classMethods;
if (name) {
// The inner name is immutable.
if (!noteDeclaredName(name, DeclarationKind::Const, namePos))
return null();
Node innerName = newName(name, namePos);
if (!innerName)
return null();
Node classBlock = finishLexicalScope(*classScope, classMethods);
if (!classBlock)
return null();
methodsOrBlock = classBlock;
// Pop the inner scope.
classScope.reset();
classStmt.reset();
Node outerName = null();
if (classContext == ClassStatement) {
// The outer name is mutable.
if (!noteDeclaredName(name, DeclarationKind::Let, namePos))
return null();
outerName = newName(name, namePos);
if (!outerName)
return null();
}
nameNode = handler.newClassNames(outerName, innerName, namePos);
if (!nameNode)
return null();
}
MOZ_ALWAYS_TRUE(setLocalStrictMode(savedStrictness));
return handler.newClass(nameNode, classHeritage, methodsOrBlock);
}
template <class ParseHandler>
bool
Parser<ParseHandler>::nextTokenContinuesLetDeclaration(TokenKind next, YieldHandling yieldHandling)
{
MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_NAME));
MOZ_ASSERT(tokenStream.currentName() == context->names().let);
MOZ_ASSERT(!tokenStream.currentToken().nameContainsEscape());
#ifdef DEBUG
TokenKind verify;
MOZ_ALWAYS_TRUE(tokenStream.peekToken(&verify));
MOZ_ASSERT(next == verify);
#endif
// Destructuring is (for once) the easy case.
if (next == TOK_LB || next == TOK_LC)
return true;
// Otherwise a let declaration must have a name.
if (next == TOK_NAME) {
if (tokenStream.nextName() == context->names().yield) {
MOZ_ASSERT(tokenStream.nextNameContainsEscape(),
"token stream should interpret unescaped 'yield' as TOK_YIELD");
// Same as |next == TOK_YIELD|.
return yieldHandling == YieldIsName;
}
// One non-"yield" TOK_NAME edge case deserves special comment.
// Consider this:
//
// let // not an ASI opportunity
// let;
//
// Static semantics in §13.3.1.1 turn a LexicalDeclaration that binds
// "let" into an early error. Does this retroactively permit ASI so
// that we should parse this as two ExpressionStatements? No. ASI
// resolves during parsing. Static semantics only apply to the full
// parse tree with ASI applied. No backsies!
return true;
}
// If we have the name "yield", the grammar parameter exactly states
// whether this is okay. (This wasn't true for SpiderMonkey's ancient
// legacy generator syntax, but that's dead now.) If YieldIsName,
// declaration-parsing code will (if necessary) enforce a strict mode
// restriction on defining "yield". If YieldIsKeyword, consider this the
// end of the declaration, in case ASI induces a semicolon that makes the
// "yield" valid.
if (next == TOK_YIELD)
return yieldHandling == YieldIsName;
// Otherwise not a let declaration.
return false;
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::variableStatement(YieldHandling yieldHandling)
{
Node vars = declarationList(yieldHandling, PNK_VAR);
if (!vars)
return null();
if (!MatchOrInsertSemicolonAfterExpression(tokenStream))
return null();
return vars;
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::statement(YieldHandling yieldHandling)
{
MOZ_ASSERT(checkOptionsCalled);
JS_CHECK_RECURSION(context, return null());
TokenKind tt;
if (!tokenStream.getToken(&tt, TokenStream::Operand))
return null();
switch (tt) {
// BlockStatement[?Yield, ?Return]
case TOK_LC:
return blockStatement(yieldHandling);
// VariableStatement[?Yield]
case TOK_VAR:
return variableStatement(yieldHandling);
// EmptyStatement
case TOK_SEMI:
return handler.newEmptyStatement(pos());
// ExpressionStatement[?Yield].
case TOK_YIELD: {
// Don't use a ternary operator here due to obscure linker issues
// around using static consts in the arms of a ternary.
TokenStream::Modifier modifier;
if (yieldExpressionsSupported())
modifier = TokenStream::Operand;
else
modifier = TokenStream::None;
TokenKind next;
if (!tokenStream.peekToken(&next, modifier))
return null();
if (next == TOK_COLON)
return labeledStatement(yieldHandling);
return expressionStatement(yieldHandling);
}
case TOK_NAME: {
TokenKind next;
if (!tokenStream.peekToken(&next))
return null();
// |let| here can only be an Identifier, not a declaration. Give nicer
// errors for declaration-looking typos.
if (!tokenStream.currentToken().nameContainsEscape() &&
tokenStream.currentName() == context->names().let)
{
bool forbiddenLetDeclaration = false;
if (pc->sc()->strict() || versionNumber() >= JSVERSION_1_7) {
// |let| can't be an Identifier in strict mode code. Ditto for
// non-standard JavaScript 1.7+.
forbiddenLetDeclaration = true;
} else if (next == TOK_LB) {
// Enforce ExpressionStatement's 'let [' lookahead restriction.
forbiddenLetDeclaration = true;
} else if (next == TOK_LC || next == TOK_NAME) {
// 'let {' and 'let foo' aren't completely forbidden, if ASI
// causes 'let' to be the entire Statement. But if they're
// same-line, we can aggressively give a better error message.
//
// Note that this ignores 'yield' as TOK_YIELD: we'll handle it
// correctly but with a worse error message.
TokenKind nextSameLine;
if (!tokenStream.peekTokenSameLine(&nextSameLine))
return null();
MOZ_ASSERT(nextSameLine == TOK_NAME ||
nextSameLine == TOK_LC ||
nextSameLine == TOK_EOL);
forbiddenLetDeclaration = nextSameLine != TOK_EOL;
}
if (forbiddenLetDeclaration) {
error(JSMSG_FORBIDDEN_AS_STATEMENT, "lexical declarations");
return null();
}
}
// NOTE: It's unfortunately allowed to have a label named 'let' in
// non-strict code. 💯
if (next == TOK_COLON)
return labeledStatement(yieldHandling);
return expressionStatement(yieldHandling);
}
case TOK_NEW:
return expressionStatement(yieldHandling, PredictInvoked);
default:
return expressionStatement(yieldHandling);
// IfStatement[?Yield, ?Return]
case TOK_IF:
return ifStatement(yieldHandling);
// BreakableStatement[?Yield, ?Return]
//
// BreakableStatement[Yield, Return]:
// IterationStatement[?Yield, ?Return]
// SwitchStatement[?Yield, ?Return]
case TOK_DO:
return doWhileStatement(yieldHandling);
case TOK_WHILE:
return whileStatement(yieldHandling);
case TOK_FOR:
return forStatement(yieldHandling);
case TOK_SWITCH:
return switchStatement(yieldHandling);
// ContinueStatement[?Yield]
case TOK_CONTINUE:
return continueStatement(yieldHandling);
// BreakStatement[?Yield]
case TOK_BREAK:
return breakStatement(yieldHandling);
// [+Return] ReturnStatement[?Yield]
case TOK_RETURN:
// The Return parameter is only used here, and the effect is easily
// detected this way, so don't bother passing around an extra parameter
// everywhere.
if (!pc->isFunctionBox()) {
error(JSMSG_BAD_RETURN_OR_YIELD, js_return_str);
return null();
}
return returnStatement(yieldHandling);
// WithStatement[?Yield, ?Return]
case TOK_WITH:
return withStatement(yieldHandling);
// LabelledStatement[?Yield, ?Return]
// This is really handled by TOK_NAME and TOK_YIELD cases above.
// ThrowStatement[?Yield]
case TOK_THROW:
return throwStatement(yieldHandling);
// TryStatement[?Yield, ?Return]
case TOK_TRY:
return tryStatement(yieldHandling);
// DebuggerStatement
case TOK_DEBUGGER:
return debuggerStatement();
// |function| is forbidden by lookahead restriction (unless as child
// statement of |if| or |else|, but Parser::consequentOrAlternative
// handles that).
case TOK_FUNCTION:
error(JSMSG_FORBIDDEN_AS_STATEMENT, "function declarations");
return null();
// |class| is also forbidden by lookahead restriction.
case TOK_CLASS:
error(JSMSG_FORBIDDEN_AS_STATEMENT, "classes");
return null();
// ImportDeclaration (only inside modules)
case TOK_IMPORT:
return importDeclaration();
// ExportDeclaration (only inside modules)
case TOK_EXPORT:
return exportDeclaration();
// Miscellaneous error cases arguably better caught here than elsewhere.
case TOK_CATCH:
error(JSMSG_CATCH_WITHOUT_TRY);
return null();
case TOK_FINALLY:
error(JSMSG_FINALLY_WITHOUT_TRY);
return null();
// NOTE: default case handled in the ExpressionStatement section.
}
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::statementListItem(YieldHandling yieldHandling,
bool canHaveDirectives /* = false */)
{
MOZ_ASSERT(checkOptionsCalled);
JS_CHECK_RECURSION(context, return null());
TokenKind tt;
if (!tokenStream.getToken(&tt, TokenStream::Operand))
return null();
switch (tt) {
// BlockStatement[?Yield, ?Return]
case TOK_LC:
return blockStatement(yieldHandling);
// VariableStatement[?Yield]
case TOK_VAR:
return variableStatement(yieldHandling);
// EmptyStatement
case TOK_SEMI:
return handler.newEmptyStatement(pos());
// ExpressionStatement[?Yield].
//
// These should probably be handled by a single ExpressionStatement
// function in a default, not split up this way.
case TOK_STRING:
if (!canHaveDirectives && tokenStream.currentToken().atom() == context->names().useAsm) {
if (!abortIfSyntaxParser())
return null();
if (!warning(JSMSG_USE_ASM_DIRECTIVE_FAIL))
return null();
}
return expressionStatement(yieldHandling);
case TOK_YIELD: {
// Don't use a ternary operator here due to obscure linker issues
// around using static consts in the arms of a ternary.
TokenStream::Modifier modifier;
if (yieldExpressionsSupported())
modifier = TokenStream::Operand;
else
modifier = TokenStream::None;
TokenKind next;
if (!tokenStream.peekToken(&next, modifier))
return null();
if (next == TOK_COLON)
return labeledStatement(yieldHandling);
return expressionStatement(yieldHandling);
}
case TOK_NAME: {
TokenKind next;
if (!tokenStream.peekToken(&next))
return null();
if (!tokenStream.currentToken().nameContainsEscape() &&
tokenStream.currentName() == context->names().let &&
nextTokenContinuesLetDeclaration(next, yieldHandling))
{
return lexicalDeclaration(yieldHandling, /* isConst = */ false);
}
if (tokenStream.currentName() == context->names().async) {
TokenKind nextSameLine = TOK_EOF;
if (!tokenStream.peekTokenSameLine(&nextSameLine))
return null();
if (nextSameLine == TOK_FUNCTION) {
uint32_t preludeStart = pos().begin;
tokenStream.consumeKnownToken(TOK_FUNCTION);
return functionStmt(preludeStart, yieldHandling, NameRequired, AsyncFunction);
}
}
if (next == TOK_COLON)
return labeledStatement(yieldHandling);
return expressionStatement(yieldHandling);
}
case TOK_NEW:
return expressionStatement(yieldHandling, PredictInvoked);
default:
return expressionStatement(yieldHandling);
// IfStatement[?Yield, ?Return]
case TOK_IF:
return ifStatement(yieldHandling);
// BreakableStatement[?Yield, ?Return]
//
// BreakableStatement[Yield, Return]:
// IterationStatement[?Yield, ?Return]
// SwitchStatement[?Yield, ?Return]
case TOK_DO:
return doWhileStatement(yieldHandling);
case TOK_WHILE:
return whileStatement(yieldHandling);
case TOK_FOR:
return forStatement(yieldHandling);
case TOK_SWITCH:
return switchStatement(yieldHandling);
// ContinueStatement[?Yield]
case TOK_CONTINUE:
return continueStatement(yieldHandling);
// BreakStatement[?Yield]
case TOK_BREAK:
return breakStatement(yieldHandling);
// [+Return] ReturnStatement[?Yield]
case TOK_RETURN:
// The Return parameter is only used here, and the effect is easily
// detected this way, so don't bother passing around an extra parameter
// everywhere.
if (!pc->isFunctionBox()) {
error(JSMSG_BAD_RETURN_OR_YIELD, js_return_str);
return null();
}
return returnStatement(yieldHandling);
// WithStatement[?Yield, ?Return]
case TOK_WITH:
return withStatement(yieldHandling);
// LabelledStatement[?Yield, ?Return]
// This is really handled by TOK_NAME and TOK_YIELD cases above.
// ThrowStatement[?Yield]
case TOK_THROW:
return throwStatement(yieldHandling);
// TryStatement[?Yield, ?Return]
case TOK_TRY:
return tryStatement(yieldHandling);
// DebuggerStatement
case TOK_DEBUGGER:
return debuggerStatement();
// Declaration[Yield]:
// HoistableDeclaration[?Yield, ~Default]
case TOK_FUNCTION:
return functionStmt(pos().begin, yieldHandling, NameRequired);
// ClassDeclaration[?Yield, ~Default]
case TOK_CLASS:
return classDefinition(yieldHandling, ClassStatement, NameRequired);
// LexicalDeclaration[In, ?Yield]
// LetOrConst BindingList[?In, ?Yield]
case TOK_CONST:
// [In] is the default behavior, because for-loops specially parse
// their heads to handle |in| in this situation.
return lexicalDeclaration(yieldHandling, /* isConst = */ true);
// ImportDeclaration (only inside modules)
case TOK_IMPORT:
return importDeclaration();
// ExportDeclaration (only inside modules)
case TOK_EXPORT:
return exportDeclaration();
// Miscellaneous error cases arguably better caught here than elsewhere.
case TOK_CATCH:
error(JSMSG_CATCH_WITHOUT_TRY);
return null();
case TOK_FINALLY:
error(JSMSG_FINALLY_WITHOUT_TRY);
return null();
// NOTE: default case handled in the ExpressionStatement section.
}
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::expr(InHandling inHandling, YieldHandling yieldHandling,
TripledotHandling tripledotHandling,
PossibleError* possibleError /* = nullptr */,
InvokedPrediction invoked /* = PredictUninvoked */)
{
Node pn = assignExpr(inHandling, yieldHandling, tripledotHandling,
possibleError, invoked);
if (!pn)
return null();
bool matched;
if (!tokenStream.matchToken(&matched, TOK_COMMA))
return null();
if (!matched)
return pn;
Node seq = handler.newCommaExpressionList(pn);
if (!seq)
return null();
while (true) {
// Trailing comma before the closing parenthesis is valid in an arrow
// function parameters list: `(a, b, ) => body`. Check if we are
// directly under CoverParenthesizedExpressionAndArrowParameterList,
// and the next two tokens are closing parenthesis and arrow. If all
// are present allow the trailing comma.
if (tripledotHandling == TripledotAllowed) {
TokenKind tt;
if (!tokenStream.peekToken(&tt, TokenStream::Operand))
return null();
if (tt == TOK_RP) {
tokenStream.consumeKnownToken(TOK_RP, TokenStream::Operand);
if (!tokenStream.peekToken(&tt))
return null();
if (tt != TOK_ARROW) {
error(JSMSG_UNEXPECTED_TOKEN, "expression", TokenKindToDesc(TOK_RP));
return null();
}
tokenStream.ungetToken(); // put back right paren
tokenStream.addModifierException(TokenStream::NoneIsOperand);
break;
}
}
// Additional calls to assignExpr should not reuse the possibleError
// which had been passed into the function. Otherwise we would lose
// information needed to determine whether or not we're dealing with
// a non-recoverable situation.
PossibleError possibleErrorInner(*this);
pn = assignExpr(inHandling, yieldHandling, tripledotHandling,
&possibleErrorInner);
if (!pn)
return null();
if (!possibleError) {
// Report any pending expression error.
if (!possibleErrorInner.checkForExpressionError())
return null();
} else {
possibleErrorInner.transferErrorsTo(possibleError);
}
handler.addList(seq, pn);
if (!tokenStream.matchToken(&matched, TOK_COMMA))
return null();
if (!matched)
break;
}
return seq;
}
static const JSOp ParseNodeKindToJSOp[] = {
JSOP_OR,
JSOP_AND,
JSOP_BITOR,
JSOP_BITXOR,
JSOP_BITAND,
JSOP_STRICTEQ,
JSOP_EQ,
JSOP_STRICTNE,
JSOP_NE,
JSOP_LT,
JSOP_LE,
JSOP_GT,
JSOP_GE,
JSOP_INSTANCEOF,
JSOP_IN,
JSOP_LSH,
JSOP_RSH,
JSOP_URSH,
JSOP_ADD,
JSOP_SUB,
JSOP_MUL,
JSOP_DIV,
JSOP_MOD,
JSOP_POW
};
static inline JSOp
BinaryOpParseNodeKindToJSOp(ParseNodeKind pnk)
{
MOZ_ASSERT(pnk >= PNK_BINOP_FIRST);
MOZ_ASSERT(pnk <= PNK_BINOP_LAST);
return ParseNodeKindToJSOp[pnk - PNK_BINOP_FIRST];
}
static ParseNodeKind
BinaryOpTokenKindToParseNodeKind(TokenKind tok)
{
MOZ_ASSERT(TokenKindIsBinaryOp(tok));
return ParseNodeKind(PNK_BINOP_FIRST + (tok - TOK_BINOP_FIRST));
}
static const int PrecedenceTable[] = {
1, /* PNK_OR */
2, /* PNK_AND */
3, /* PNK_BITOR */
4, /* PNK_BITXOR */
5, /* PNK_BITAND */
6, /* PNK_STRICTEQ */
6, /* PNK_EQ */
6, /* PNK_STRICTNE */
6, /* PNK_NE */
7, /* PNK_LT */
7, /* PNK_LE */
7, /* PNK_GT */
7, /* PNK_GE */
7, /* PNK_INSTANCEOF */
7, /* PNK_IN */
8, /* PNK_LSH */
8, /* PNK_RSH */
8, /* PNK_URSH */
9, /* PNK_ADD */
9, /* PNK_SUB */
10, /* PNK_STAR */
10, /* PNK_DIV */
10, /* PNK_MOD */
11 /* PNK_POW */
};
static const int PRECEDENCE_CLASSES = 11;
static int
Precedence(ParseNodeKind pnk) {
// Everything binds tighter than PNK_LIMIT, because we want to reduce all
// nodes to a single node when we reach a token that is not another binary
// operator.
if (pnk == PNK_LIMIT)
return 0;
MOZ_ASSERT(pnk >= PNK_BINOP_FIRST);
MOZ_ASSERT(pnk <= PNK_BINOP_LAST);
return PrecedenceTable[pnk - PNK_BINOP_FIRST];
}
template <typename ParseHandler>
MOZ_ALWAYS_INLINE typename ParseHandler::Node
Parser<ParseHandler>::orExpr1(InHandling inHandling, YieldHandling yieldHandling,
TripledotHandling tripledotHandling,
PossibleError* possibleError,
InvokedPrediction invoked /* = PredictUninvoked */)
{
// Shift-reduce parser for the binary operator part of the JS expression
// syntax.
// Conceptually there's just one stack, a stack of pairs (lhs, op).
// It's implemented using two separate arrays, though.
Node nodeStack[PRECEDENCE_CLASSES];
ParseNodeKind kindStack[PRECEDENCE_CLASSES];
int depth = 0;
Node pn;
for (;;) {
pn = unaryExpr(yieldHandling, tripledotHandling, possibleError, invoked);
if (!pn)
return pn;
// If a binary operator follows, consume it and compute the
// corresponding operator.
TokenKind tok;
if (!tokenStream.getToken(&tok))
return null();
ParseNodeKind pnk;
if (tok == TOK_IN ? inHandling == InAllowed : TokenKindIsBinaryOp(tok)) {
// We're definitely not in a destructuring context, so report any
// pending expression error now.
if (possibleError && !possibleError->checkForExpressionError())
return null();
// Report an error for unary expressions on the LHS of **.
if (tok == TOK_POW && handler.isUnparenthesizedUnaryExpression(pn)) {
error(JSMSG_BAD_POW_LEFTSIDE);
return null();
}
pnk = BinaryOpTokenKindToParseNodeKind(tok);
} else {
tok = TOK_EOF;
pnk = PNK_LIMIT;
}
// From this point on, destructuring defaults are definitely an error.
possibleError = nullptr;
// If pnk has precedence less than or equal to another operator on the
// stack, reduce. This combines nodes on the stack until we form the
// actual lhs of pnk.
//
// The >= in this condition works because it is appendOrCreateList's
// job to decide if the operator in question is left- or
// right-associative, and build the corresponding tree.
while (depth > 0 && Precedence(kindStack[depth - 1]) >= Precedence(pnk)) {
depth--;
ParseNodeKind combiningPnk = kindStack[depth];
JSOp combiningOp = BinaryOpParseNodeKindToJSOp(combiningPnk);
pn = handler.appendOrCreateList(combiningPnk, nodeStack[depth], pn, pc, combiningOp);
if (!pn)
return pn;
}
if (pnk == PNK_LIMIT)
break;
nodeStack[depth] = pn;
kindStack[depth] = pnk;
depth++;
MOZ_ASSERT(depth <= PRECEDENCE_CLASSES);
}
MOZ_ASSERT(depth == 0);
return pn;
}
template <typename ParseHandler>
MOZ_ALWAYS_INLINE typename ParseHandler::Node
Parser<ParseHandler>::condExpr1(InHandling inHandling, YieldHandling yieldHandling,
TripledotHandling tripledotHandling,
PossibleError* possibleError,
InvokedPrediction invoked /* = PredictUninvoked */)
{
Node condition = orExpr1(inHandling, yieldHandling, tripledotHandling, possibleError, invoked);
if (!condition || !tokenStream.isCurrentTokenType(TOK_HOOK))
return condition;
Node thenExpr = assignExpr(InAllowed, yieldHandling, TripledotProhibited);
if (!thenExpr)
return null();
MUST_MATCH_TOKEN(TOK_COLON, JSMSG_COLON_IN_COND);
Node elseExpr = assignExpr(inHandling, yieldHandling, TripledotProhibited);
if (!elseExpr)
return null();
// Advance to the next token; the caller is responsible for interpreting it.
TokenKind ignored;
if (!tokenStream.getToken(&ignored))
return null();
return handler.newConditional(condition, thenExpr, elseExpr);
}
class AutoClearInDestructuringDecl
{
ParseContext* pc_;
Maybe<DeclarationKind> saved_;
public:
explicit AutoClearInDestructuringDecl(ParseContext* pc)
: pc_(pc),
saved_(pc->inDestructuringDecl)
{
pc->inDestructuringDecl = Nothing();
if (saved_ && *saved_ == DeclarationKind::FormalParameter)
pc->functionBox()->hasParameterExprs = true;
}
~AutoClearInDestructuringDecl() {
pc_->inDestructuringDecl = saved_;
}
};
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::assignExpr(InHandling inHandling, YieldHandling yieldHandling,
TripledotHandling tripledotHandling,
PossibleError* possibleError /* = nullptr */,
InvokedPrediction invoked /* = PredictUninvoked */)
{
JS_CHECK_RECURSION(context, return null());
// It's very common at this point to have a "detectably simple" expression,
// i.e. a name/number/string token followed by one of the following tokens
// that obviously isn't part of an expression: , ; : ) ] }
//
// (In Parsemark this happens 81.4% of the time; in code with large
// numeric arrays, such as some Kraken benchmarks, it happens more often.)
//
// In such cases, we can avoid the full expression parsing route through
// assignExpr(), condExpr1(), orExpr1(), unaryExpr(), memberExpr(), and
// primaryExpr().
TokenKind tt;
if (!tokenStream.getToken(&tt, TokenStream::Operand))
return null();
uint32_t exprOffset = pos().begin;
bool endsExpr;
if (tt == TOK_NAME) {
if (!tokenStream.nextTokenEndsExpr(&endsExpr))
return null();
if (endsExpr) {
Rooted<PropertyName*> name(context, identifierReference(yieldHandling));
if (!name)
return null();
return identifierReference(name);
}
}
if (tt == TOK_NUMBER) {
if (!tokenStream.nextTokenEndsExpr(&endsExpr))
return null();
if (endsExpr)
return newNumber(tokenStream.currentToken());
}
if (tt == TOK_STRING) {
if (!tokenStream.nextTokenEndsExpr(&endsExpr))
return null();
if (endsExpr)
return stringLiteral();
}
if (tt == TOK_YIELD && yieldExpressionsSupported())
return yieldExpression(inHandling);
bool maybeAsyncArrow = false;
if (tt == TOK_NAME && tokenStream.currentName() == context->names().async) {
TokenKind nextSameLine = TOK_EOF;
if (!tokenStream.peekTokenSameLine(&nextSameLine))
return null();
if (nextSameLine == TOK_NAME || nextSameLine == TOK_YIELD)
maybeAsyncArrow = true;
}
tokenStream.ungetToken();
// Save the tokenizer state in case we find an arrow function and have to
// rewind.
TokenStream::Position start(keepAtoms);
tokenStream.tell(&start);
PossibleError possibleErrorInner(*this);
Node lhs;
if (maybeAsyncArrow) {
tokenStream.consumeKnownToken(TOK_NAME, TokenStream::Operand);
MOZ_ASSERT(tokenStream.currentName() == context->names().async);
TokenKind tt;
if (!tokenStream.getToken(&tt))
return null();
MOZ_ASSERT(tt == TOK_NAME || tt == TOK_YIELD);
// Check yield validity here.
RootedPropertyName name(context, bindingIdentifier(yieldHandling));
if (!name)
return null();
if (!tokenStream.getToken(&tt))
return null();
if (tt != TOK_ARROW) {
error(JSMSG_UNEXPECTED_TOKEN, "'=>' after argument list", TokenKindToDesc(tt));
return null();
}
} else {
lhs = condExpr1(inHandling, yieldHandling, tripledotHandling, &possibleErrorInner, invoked);
if (!lhs) {
return null();
}
}
ParseNodeKind kind;
JSOp op;
switch (tokenStream.currentToken().type) {
case TOK_ASSIGN: kind = PNK_ASSIGN; op = JSOP_NOP; break;
case TOK_ADDASSIGN: kind = PNK_ADDASSIGN; op = JSOP_ADD; break;
case TOK_SUBASSIGN: kind = PNK_SUBASSIGN; op = JSOP_SUB; break;
case TOK_BITORASSIGN: kind = PNK_BITORASSIGN; op = JSOP_BITOR; break;
case TOK_BITXORASSIGN: kind = PNK_BITXORASSIGN; op = JSOP_BITXOR; break;
case TOK_BITANDASSIGN: kind = PNK_BITANDASSIGN; op = JSOP_BITAND; break;
case TOK_LSHASSIGN: kind = PNK_LSHASSIGN; op = JSOP_LSH; break;
case TOK_RSHASSIGN: kind = PNK_RSHASSIGN; op = JSOP_RSH; break;
case TOK_URSHASSIGN: kind = PNK_URSHASSIGN; op = JSOP_URSH; break;
case TOK_MULASSIGN: kind = PNK_MULASSIGN; op = JSOP_MUL; break;
case TOK_DIVASSIGN: kind = PNK_DIVASSIGN; op = JSOP_DIV; break;
case TOK_MODASSIGN: kind = PNK_MODASSIGN; op = JSOP_MOD; break;
case TOK_POWASSIGN: kind = PNK_POWASSIGN; op = JSOP_POW; break;
case TOK_ARROW: {
// A line terminator between ArrowParameters and the => should trigger a SyntaxError.
tokenStream.ungetToken();
TokenKind next;
if (!tokenStream.peekTokenSameLine(&next))
return null();
MOZ_ASSERT(next == TOK_ARROW || next == TOK_EOL);
if (next != TOK_ARROW) {
error(JSMSG_LINE_BREAK_BEFORE_ARROW);
return null();
}
tokenStream.consumeKnownToken(TOK_ARROW);
bool isBlock = false;
if (!tokenStream.peekToken(&next, TokenStream::Operand))
return null();
if (next == TOK_LC)
isBlock = true;
tokenStream.seek(start);
if (!tokenStream.getToken(&next, TokenStream::Operand))
return null();
uint32_t preludeStart = pos().begin;
tokenStream.ungetToken();
GeneratorKind generatorKind = NotGenerator;
FunctionAsyncKind asyncKind = SyncFunction;
if (next == TOK_NAME) {
tokenStream.consumeKnownToken(next, TokenStream::Operand);
if (tokenStream.currentName() == context->names().async) {
TokenKind nextSameLine = TOK_EOF;
if (!tokenStream.peekTokenSameLine(&nextSameLine))
return null();
if (nextSameLine == TOK_ARROW) {
tokenStream.ungetToken();
} else {
generatorKind = StarGenerator;
asyncKind = AsyncFunction;
}
} else {
tokenStream.ungetToken();
}
}
Node pn = handler.newArrowFunction();
if (!pn)
return null();
Node arrowFunc = functionDefinition(preludeStart, pn, inHandling, yieldHandling, nullptr,
Arrow, generatorKind, asyncKind);
if (!arrowFunc)
return null();
if (isBlock) {
// This arrow function could be a non-trailing member of a comma
// expression or a semicolon terminating a full expression. If so,
// the next token is that comma/semicolon, gotten with None:
//
// a => {}, b; // as if (a => {}), b;
// a => {};
//
// But if this arrow function ends a statement, ASI permits the
// next token to start an expression statement. In that case the
// next token must be gotten as Operand:
//
// a => {} // complete expression statement
// /x/g; // regular expression as a statement, *not* division
//
// Getting the second case right requires the first token-peek
// after the arrow function use Operand, and that peek must occur
// before Parser::expr() looks for a comma. Do so here, then
// immediately add the modifier exception needed for the first
// case.
//
// Note that the second case occurs *only* if the arrow function
// has block body. An arrow function not ending in such, ends in
// another AssignmentExpression that we can inductively assume was
// peeked consistently.
TokenKind ignored;
if (!tokenStream.peekToken(&ignored, TokenStream::Operand))
return null();
tokenStream.addModifierException(TokenStream::NoneIsOperand);
}
return arrowFunc;
}
default:
MOZ_ASSERT(!tokenStream.isCurrentTokenAssignment());
if (!possibleError) {
if (!possibleErrorInner.checkForExpressionError())
return null();
} else {
possibleErrorInner.transferErrorsTo(possibleError);
}
tokenStream.ungetToken();
return lhs;
}
// Verify the left-hand side expression doesn't have a forbidden form.
if (handler.isUnparenthesizedDestructuringPattern(lhs)) {
if (kind != PNK_ASSIGN) {
error(JSMSG_BAD_DESTRUCT_ASS);
return null();
}
if (!checkDestructuringPattern(lhs, Nothing(), &possibleErrorInner))
return null();
} else if (handler.isNameAnyParentheses(lhs)) {
if (const char* chars = handler.nameIsArgumentsEvalAnyParentheses(lhs, context)) {
// |chars| is "arguments" or "eval" here.
if (!strictModeErrorAt(exprOffset, JSMSG_BAD_STRICT_ASSIGN, chars))
return null();
}
handler.adjustGetToSet(lhs);
} else if (handler.isPropertyAccess(lhs)) {
// Permitted: no additional testing/fixup needed.
} else if (handler.isFunctionCall(lhs)) {
if (!strictModeErrorAt(exprOffset, JSMSG_BAD_LEFTSIDE_OF_ASS))
return null();
} else {
errorAt(exprOffset, JSMSG_BAD_LEFTSIDE_OF_ASS);
return null();
}
if (!possibleErrorInner.checkForExpressionError())
return null();
Node rhs;
{
AutoClearInDestructuringDecl autoClear(pc);
rhs = assignExpr(inHandling, yieldHandling, TripledotProhibited);
if (!rhs)
return null();
}
if (kind == PNK_ASSIGN)
handler.checkAndSetIsDirectRHSAnonFunction(rhs);
return handler.newAssignment(kind, lhs, rhs, op);
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::isValidSimpleAssignmentTarget(Node node,
FunctionCallBehavior behavior /* = ForbidAssignmentToFunctionCalls */)
{
// Note that this method implements *only* a boolean test. Reporting an
// error for the various syntaxes that fail this, and warning for the
// various syntaxes that "pass" this but should not, occurs elsewhere.
if (handler.isNameAnyParentheses(node)) {
if (!pc->sc()->strict())
return true;
return !handler.nameIsArgumentsEvalAnyParentheses(node, context);
}
if (handler.isPropertyAccess(node))
return true;
if (behavior == PermitAssignmentToFunctionCalls) {
if (handler.isFunctionCall(node))
return true;
}
return false;
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::checkIncDecOperand(Node operand, uint32_t operandOffset)
{
if (handler.isNameAnyParentheses(operand)) {
if (const char* chars = handler.nameIsArgumentsEvalAnyParentheses(operand, context)) {
if (!strictModeErrorAt(operandOffset, JSMSG_BAD_STRICT_ASSIGN, chars))
return false;
}
} else if (handler.isPropertyAccess(operand)) {
// Permitted: no additional testing/fixup needed.
} else if (handler.isFunctionCall(operand)) {
// Assignment to function calls is forbidden in ES6. We're still
// somewhat concerned about sites using this in dead code, so forbid it
// only in strict mode code (or if the werror option has been set), and
// otherwise warn.
if (!strictModeErrorAt(operandOffset, JSMSG_BAD_INCOP_OPERAND))
return false;
} else {
errorAt(operandOffset, JSMSG_BAD_INCOP_OPERAND);
return false;
}
MOZ_ASSERT(isValidSimpleAssignmentTarget(operand, PermitAssignmentToFunctionCalls),
"inconsistent increment/decrement operand validation");
return true;
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::unaryOpExpr(YieldHandling yieldHandling, ParseNodeKind kind, JSOp op,
uint32_t begin)
{
Node kid = unaryExpr(yieldHandling, TripledotProhibited);
if (!kid)
return null();
return handler.newUnary(kind, op, begin, kid);
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::unaryExpr(YieldHandling yieldHandling, TripledotHandling tripledotHandling,
PossibleError* possibleError /* = nullptr */,
InvokedPrediction invoked /* = PredictUninvoked */)
{
JS_CHECK_RECURSION(context, return null());
TokenKind tt;
if (!tokenStream.getToken(&tt, TokenStream::Operand))
return null();
uint32_t begin = pos().begin;
switch (tt) {
case TOK_VOID:
return unaryOpExpr(yieldHandling, PNK_VOID, JSOP_VOID, begin);
case TOK_NOT:
return unaryOpExpr(yieldHandling, PNK_NOT, JSOP_NOT, begin);
case TOK_BITNOT:
return unaryOpExpr(yieldHandling, PNK_BITNOT, JSOP_BITNOT, begin);
case TOK_ADD:
return unaryOpExpr(yieldHandling, PNK_POS, JSOP_POS, begin);
case TOK_SUB:
return unaryOpExpr(yieldHandling, PNK_NEG, JSOP_NEG, begin);
case TOK_TYPEOF: {
// The |typeof| operator is specially parsed to distinguish its
// application to a name, from its application to a non-name
// expression:
//
// // Looks up the name, doesn't find it and so evaluates to
// // "undefined".
// assertEq(typeof nonExistentName, "undefined");
//
// // Evaluates expression, triggering a runtime ReferenceError for
// // the undefined name.
// typeof (1, nonExistentName);
Node kid = unaryExpr(yieldHandling, TripledotProhibited);
if (!kid)
return null();
return handler.newTypeof(begin, kid);
}
case TOK_INC:
case TOK_DEC:
{
TokenKind tt2;
if (!tokenStream.getToken(&tt2, TokenStream::Operand))
return null();
uint32_t operandOffset = pos().begin;
Node operand = memberExpr(yieldHandling, TripledotProhibited, tt2);
if (!operand || !checkIncDecOperand(operand, operandOffset))
return null();
return handler.newUpdate((tt == TOK_INC) ? PNK_PREINCREMENT : PNK_PREDECREMENT,
begin, operand);
}
case TOK_DELETE: {
uint32_t exprOffset;
if (!tokenStream.peekOffset(&exprOffset, TokenStream::Operand))
return null();
Node expr = unaryExpr(yieldHandling, TripledotProhibited);
if (!expr)
return null();
// Per spec, deleting any unary expression is valid -- it simply
// returns true -- except for one case that is illegal in strict mode.
if (handler.isNameAnyParentheses(expr)) {
if (!strictModeErrorAt(exprOffset, JSMSG_DEPRECATED_DELETE_OPERAND))
return null();
pc->sc()->setBindingsAccessedDynamically();
}
return handler.newDelete(begin, expr);
}
case TOK_AWAIT: {
if (!pc->isAsync()) {
// TOK_AWAIT can be returned in module, even if it's not inside
// async function.
error(JSMSG_RESERVED_ID, "await");
return null();
}
Node kid = unaryExpr(yieldHandling, tripledotHandling, possibleError, invoked);
if (!kid)
return null();
pc->lastAwaitOffset = begin;
return newAwaitExpression(begin, kid);
}
default: {
Node expr = memberExpr(yieldHandling, tripledotHandling, tt, /* allowCallSyntax = */ true,
possibleError, invoked);
if (!expr)
return null();
/* Don't look across a newline boundary for a postfix incop. */
if (!tokenStream.peekTokenSameLine(&tt))
return null();
if (tt != TOK_INC && tt != TOK_DEC)
return expr;
tokenStream.consumeKnownToken(tt);
if (!checkIncDecOperand(expr, begin))
return null();
return handler.newUpdate((tt == TOK_INC) ? PNK_POSTINCREMENT : PNK_POSTDECREMENT,
begin, expr);
}
}
}
/*** Comprehensions *******************************************************************************
*
* We currently support two flavors of comprehensions, all deprecated:
*
* [for (V of OBJ) if (COND) EXPR] // ES6-era array comprehension
* (for (V of OBJ) if (COND) EXPR) // ES6-era generator expression
*
* (These flavors are called "ES6-era" because they were in ES6 draft
* specifications for a while. Shortly after this syntax was implemented in SM,
* TC39 decided to drop it.)
*/
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::generatorComprehensionLambda(unsigned begin)
{
Node genfn = handler.newFunctionExpression();
if (!genfn)
return null();
ParseContext* outerpc = pc;
// If we are off the main thread, the generator meta-objects have
// already been created by js::StartOffThreadParseScript, so cx will not
// be necessary.
RootedObject proto(context);
JSContext* cx = context->maybeJSContext();
proto = GlobalObject::getOrCreateStarGeneratorFunctionPrototype(cx, context->global());
if (!proto)
return null();
RootedFunction fun(context, newFunction(/* atom = */ nullptr, Expression,
StarGenerator, SyncFunction, proto));
if (!fun)
return null();
// Create box for fun->object early to root it.
Directives directives(/* strict = */ outerpc->sc()->strict());
FunctionBox* genFunbox = newFunctionBox(genfn, fun, /* preludeStart = */ 0, directives,
StarGenerator, SyncFunction, /* tryAnnexB = */ false);
if (!genFunbox)
return null();
genFunbox->isGenexpLambda = true;
genFunbox->initWithEnclosingParseContext(outerpc, Expression);
ParseContext genpc(this, genFunbox, /* newDirectives = */ nullptr);
if (!genpc.init())
return null();
genpc.functionScope().useAsVarScope(&genpc);
/*
* We assume conservatively that any deoptimization flags in pc->sc()
* come from the kid. So we propagate these flags into genfn. For code
* simplicity we also do not detect if the flags were only set in the
* kid and could be removed from pc->sc().
*/
genFunbox->anyCxFlags = outerpc->sc()->anyCxFlags;
if (!declareDotGeneratorName())
return null();
Node body = handler.newStatementList(TokenPos(begin, pos().end));
if (!body)
return null();
Node comp = comprehension(StarGenerator);
if (!comp)
return null();
MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_IN_PAREN);
uint32_t end = pos().end;
handler.setBeginPosition(comp, begin);
handler.setEndPosition(comp, end);
genFunbox->bufEnd = end;
handler.addStatementToList(body, comp);
handler.setEndPosition(body, end);
handler.setBeginPosition(genfn, begin);
handler.setEndPosition(genfn, end);
Node generator = newDotGeneratorName();
if (!generator)
return null();
if (!handler.prependInitialYield(body, generator))
return null();
if (!propagateFreeNamesAndMarkClosedOverBindings(pc->varScope()))
return null();
if (!finishFunction())
return null();
if (!leaveInnerFunction(outerpc))
return null();
// Note that if we ever start syntax-parsing generators, we will also
// need to propagate the closed-over variable set to the inner
// lazyscript.
if (!handler.setComprehensionLambdaBody(genfn, body))
return null();
return genfn;
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::comprehensionFor(GeneratorKind comprehensionKind)
{
MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_FOR));
uint32_t begin = pos().begin;
MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_AFTER_FOR);
// FIXME: Destructuring binding (bug 980828).
MUST_MATCH_TOKEN(TOK_NAME, JSMSG_NO_VARIABLE_NAME);
RootedPropertyName name(context, tokenStream.currentName());
if (name == context->names().let) {
error(JSMSG_LET_COMP_BINDING);
return null();
}
TokenPos namePos = pos();
Node lhs = newName(name);
if (!lhs)
return null();
bool matched;
if (!tokenStream.matchContextualKeyword(&matched, context->names().of))
return null();
if (!matched) {
error(JSMSG_OF_AFTER_FOR_NAME);
return null();
}
Node rhs = assignExpr(InAllowed, YieldIsKeyword, TripledotProhibited);
if (!rhs)
return null();
MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_FOR_OF_ITERABLE);
TokenPos headPos(begin, pos().end);
ParseContext::Scope scope(this);
if (!scope.init(pc))
return null();
{
// Push a temporary ForLoopLexicalHead Statement that allows for
// lexical declarations, as they are usually allowed only in braced
// statements.
ParseContext::Statement forHeadStmt(pc, StatementKind::ForLoopLexicalHead);
if (!noteDeclaredName(name, DeclarationKind::Let, namePos))
return null();
}
Node decls = handler.newComprehensionBinding(lhs);
if (!decls)
return null();
Node tail = comprehensionTail(comprehensionKind);
if (!tail)
return null();
// Finish the lexical scope after parsing the tail.
Node lexicalScope = finishLexicalScope(scope, decls);
if (!lexicalScope)
return null();
Node head = handler.newForInOrOfHead(PNK_FOROF, lexicalScope, rhs, headPos);
if (!head)
return null();
return handler.newComprehensionFor(begin, head, tail);
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::comprehensionIf(GeneratorKind comprehensionKind)
{
MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_IF));
uint32_t begin = pos().begin;
MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_COND);
Node cond = assignExpr(InAllowed, YieldIsKeyword, TripledotProhibited);
if (!cond)
return null();
MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_COND);
/* Check for (a = b) and warn about possible (a == b) mistype. */
if (handler.isUnparenthesizedAssignment(cond)) {
if (!extraWarning(JSMSG_EQUAL_AS_ASSIGN))
return null();
}
Node then = comprehensionTail(comprehensionKind);
if (!then)
return null();
return handler.newIfStatement(begin, cond, then, null());
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::comprehensionTail(GeneratorKind comprehensionKind)
{
JS_CHECK_RECURSION(context, return null());
bool matched;
if (!tokenStream.matchToken(&matched, TOK_FOR, TokenStream::Operand))
return null();
if (matched)
return comprehensionFor(comprehensionKind);
if (!tokenStream.matchToken(&matched, TOK_IF, TokenStream::Operand))
return null();
if (matched)
return comprehensionIf(comprehensionKind);
uint32_t begin = pos().begin;
Node bodyExpr = assignExpr(InAllowed, YieldIsKeyword, TripledotProhibited);
if (!bodyExpr)
return null();
if (comprehensionKind == NotGenerator)
return handler.newArrayPush(begin, bodyExpr);
MOZ_ASSERT(comprehensionKind == StarGenerator);
Node yieldExpr = newYieldExpression(begin, bodyExpr);
if (!yieldExpr)
return null();
yieldExpr = handler.parenthesize(yieldExpr);
return handler.newExprStatement(yieldExpr, pos().end);
}
// Parse an ES6-era generator or array comprehension, starting at the first
// `for`. The caller is responsible for matching the ending TOK_RP or TOK_RB.
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::comprehension(GeneratorKind comprehensionKind)
{
MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_FOR));
uint32_t startYieldOffset = pc->lastYieldOffset;
Node body = comprehensionFor(comprehensionKind);
if (!body)
return null();
if (comprehensionKind != NotGenerator && pc->lastYieldOffset != startYieldOffset) {
errorAt(pc->lastYieldOffset, JSMSG_BAD_GENEXP_BODY, js_yield_str);
return null();
}
return body;
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::arrayComprehension(uint32_t begin)
{
Node inner = comprehension(NotGenerator);
if (!inner)
return null();
MUST_MATCH_TOKEN(TOK_RB, JSMSG_BRACKET_AFTER_ARRAY_COMPREHENSION);
Node comp = handler.newList(PNK_ARRAYCOMP, inner);
if (!comp)
return null();
handler.setBeginPosition(comp, begin);
handler.setEndPosition(comp, pos().end);
return comp;
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::generatorComprehension(uint32_t begin)
{
MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_FOR));
// We have no problem parsing generator comprehensions inside lazy
// functions, but the bytecode emitter currently can't handle them that way,
// because when it goes to emit the code for the inner generator function,
// it expects outer functions to have non-lazy scripts.
if (!abortIfSyntaxParser())
return null();
Node genfn = generatorComprehensionLambda(begin);
if (!genfn)
return null();
Node result = handler.newList(PNK_GENEXP, genfn, JSOP_CALL);
if (!result)
return null();
handler.setBeginPosition(result, begin);
handler.setEndPosition(result, pos().end);
return result;
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::assignExprWithoutYieldOrAwait(YieldHandling yieldHandling)
{
uint32_t startYieldOffset = pc->lastYieldOffset;
uint32_t startAwaitOffset = pc->lastAwaitOffset;
Node res = assignExpr(InAllowed, yieldHandling, TripledotProhibited);
if (res) {
if (pc->lastYieldOffset != startYieldOffset) {
errorAt(pc->lastYieldOffset, JSMSG_YIELD_IN_DEFAULT);
return null();
}
if (pc->lastAwaitOffset != startAwaitOffset) {
errorAt(pc->lastAwaitOffset, JSMSG_AWAIT_IN_DEFAULT);
return null();
}
}
return res;
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::argumentList(YieldHandling yieldHandling, Node listNode, bool* isSpread,
PossibleError* possibleError /* = nullptr */)
{
bool matched;
if (!tokenStream.matchToken(&matched, TOK_RP, TokenStream::Operand))
return false;
if (matched) {
handler.setEndPosition(listNode, pos().end);
return true;
}
while (true) {
bool spread = false;
uint32_t begin = 0;
if (!tokenStream.matchToken(&matched, TOK_TRIPLEDOT, TokenStream::Operand))
return false;
if (matched) {
spread = true;
begin = pos().begin;
*isSpread = true;
}
Node argNode = assignExpr(InAllowed, yieldHandling, TripledotProhibited, possibleError);
if (!argNode)
return false;
if (spread) {
argNode = handler.newSpread(begin, argNode);
if (!argNode)
return false;
}
handler.addList(listNode, argNode);
bool matched;
if (!tokenStream.matchToken(&matched, TOK_COMMA))
return false;
if (!matched)
break;
TokenKind tt;
if (!tokenStream.peekToken(&tt, TokenStream::Operand))
return null();
if (tt == TOK_RP) {
tokenStream.addModifierException(TokenStream::NoneIsOperand);
break;
}
}
MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_ARGS);
handler.setEndPosition(listNode, pos().end);
return true;
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::checkAndMarkSuperScope()
{
if (!pc->sc()->allowSuperProperty())
return false;
pc->setSuperScopeNeedsHomeObject();
return true;
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::memberExpr(YieldHandling yieldHandling, TripledotHandling tripledotHandling,
TokenKind tt, bool allowCallSyntax /* = true */,
PossibleError* possibleError /* = nullptr */,
InvokedPrediction invoked /* = PredictUninvoked */)
{
MOZ_ASSERT(tokenStream.isCurrentTokenType(tt));
Node lhs;
JS_CHECK_RECURSION(context, return null());
/* Check for new expression first. */
if (tt == TOK_NEW) {
uint32_t newBegin = pos().begin;
// Make sure this wasn't a |new.target| in disguise.
Node newTarget;
if (!tryNewTarget(newTarget))
return null();
if (newTarget) {
lhs = newTarget;
} else {
lhs = handler.newList(PNK_NEW, newBegin, JSOP_NEW);
if (!lhs)
return null();
// Gotten by tryNewTarget
tt = tokenStream.currentToken().type;
Node ctorExpr = memberExpr(yieldHandling, TripledotProhibited, tt,
/* allowCallSyntax = */ false,
/* possibleError = */ nullptr, PredictInvoked);
if (!ctorExpr)
return null();
handler.addList(lhs, ctorExpr);
bool matched;
if (!tokenStream.matchToken(&matched, TOK_LP))
return null();
if (matched) {
bool isSpread = false;
if (!argumentList(yieldHandling, lhs, &isSpread))
return null();
if (isSpread)
handler.setOp(lhs, JSOP_SPREADNEW);
}
}
} else if (tt == TOK_SUPER) {
Node thisName = newThisName();
if (!thisName)
return null();
lhs = handler.newSuperBase(thisName, pos());
if (!lhs)
return null();
} else {
lhs = primaryExpr(yieldHandling, tripledotHandling, tt, possibleError, invoked);
if (!lhs)
return null();
}
MOZ_ASSERT_IF(handler.isSuperBase(lhs), tokenStream.isCurrentTokenType(TOK_SUPER));
while (true) {
if (!tokenStream.getToken(&tt))
return null();
if (tt == TOK_EOF)
break;
Node nextMember;
if (tt == TOK_DOT) {
if (!tokenStream.getToken(&tt, TokenStream::KeywordIsName))
return null();
if (tt == TOK_NAME) {
PropertyName* field = tokenStream.currentName();
if (handler.isSuperBase(lhs) && !checkAndMarkSuperScope()) {
error(JSMSG_BAD_SUPERPROP, "property");
return null();
}
nextMember = handler.newPropertyAccess(lhs, field, pos().end);
if (!nextMember)
return null();
} else {
error(JSMSG_NAME_AFTER_DOT);
return null();
}
} else if (tt == TOK_LB) {
Node propExpr = expr(InAllowed, yieldHandling, TripledotProhibited);
if (!propExpr)
return null();
MUST_MATCH_TOKEN(TOK_RB, JSMSG_BRACKET_IN_INDEX);
if (handler.isSuperBase(lhs) && !checkAndMarkSuperScope()) {
error(JSMSG_BAD_SUPERPROP, "member");
return null();
}
nextMember = handler.newPropertyByValue(lhs, propExpr, pos().end);
if (!nextMember)
return null();
} else if ((allowCallSyntax && tt == TOK_LP) ||
tt == TOK_TEMPLATE_HEAD ||
tt == TOK_NO_SUBS_TEMPLATE)
{
if (handler.isSuperBase(lhs)) {
if (!pc->sc()->allowSuperCall()) {
error(JSMSG_BAD_SUPERCALL);
return null();
}
if (tt != TOK_LP) {
error(JSMSG_BAD_SUPER);
return null();
}
nextMember = handler.newList(PNK_SUPERCALL, lhs, JSOP_SUPERCALL);
if (!nextMember)
return null();
// Despite the fact that it's impossible to have |super()| in a
// generator, we still inherit the yieldHandling of the
// memberExpression, per spec. Curious.
bool isSpread = false;
if (!argumentList(yieldHandling, nextMember, &isSpread))
return null();
if (isSpread)
handler.setOp(nextMember, JSOP_SPREADSUPERCALL);
Node thisName = newThisName();
if (!thisName)
return null();
nextMember = handler.newSetThis(thisName, nextMember);
if (!nextMember)
return null();
} else {
if (options().selfHostingMode && handler.isPropertyAccess(lhs)) {
error(JSMSG_SELFHOSTED_METHOD_CALL);
return null();
}
nextMember = tt == TOK_LP ? handler.newCall() : handler.newTaggedTemplate();
if (!nextMember)
return null();
JSOp op = JSOP_CALL;
bool maybeAsyncArrow = false;
if (tt == TOK_LP && handler.isNameAnyParentheses(lhs)) {
if (handler.nameIsEvalAnyParentheses(lhs, context)) {
// Select the right EVAL op and flag pc as having a
// direct eval.
op = pc->sc()->strict() ? JSOP_STRICTEVAL : JSOP_EVAL;
pc->sc()->setBindingsAccessedDynamically();
pc->sc()->setHasDirectEval();
// In non-strict mode code, direct calls to eval can
// add variables to the call object.
if (pc->isFunctionBox() && !pc->sc()->strict())
pc->functionBox()->setHasExtensibleScope();
// If we're in a method, mark the method as requiring
// support for 'super', since direct eval code can use
// it. (If we're not in a method, that's fine, so
// ignore the return value.)
checkAndMarkSuperScope();
} else if (handler.nameIsUnparenthesizedAsync(lhs, context)) {
// |async (| can be the start of an async arrow
// function, so we need to defer reporting possible
// errors from destructuring syntax. To give better
// error messages, we only allow the AsyncArrowHead
// part of the CoverCallExpressionAndAsyncArrowHead
// syntax when the initial name is "async".
maybeAsyncArrow = true;
}
} else if (PropertyName* prop = handler.maybeDottedProperty(lhs)) {
// Use the JSOP_FUN{APPLY,CALL} optimizations given the
// right syntax.
if (prop == context->names().apply) {
op = JSOP_FUNAPPLY;
if (pc->isFunctionBox())
pc->functionBox()->usesApply = true;
} else if (prop == context->names().call) {
op = JSOP_FUNCALL;
}
}
handler.setBeginPosition(nextMember, lhs);
handler.addList(nextMember, lhs);
if (tt == TOK_LP) {
bool isSpread = false;
PossibleError* asyncPossibleError = maybeAsyncArrow ? possibleError : nullptr;
if (!argumentList(yieldHandling, nextMember, &isSpread, asyncPossibleError))
return null();
if (isSpread) {
if (op == JSOP_EVAL)
op = JSOP_SPREADEVAL;
else if (op == JSOP_STRICTEVAL)
op = JSOP_STRICTSPREADEVAL;
else
op = JSOP_SPREADCALL;
}
} else {
if (!taggedTemplate(yieldHandling, nextMember, tt))
return null();
}
handler.setOp(nextMember, op);
}
} else {
tokenStream.ungetToken();
if (handler.isSuperBase(lhs))
break;
return lhs;
}
lhs = nextMember;
}
if (handler.isSuperBase(lhs)) {
error(JSMSG_BAD_SUPER);
return null();
}
return lhs;
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::newName(PropertyName* name)
{
return newName(name, pos());
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::newName(PropertyName* name, TokenPos pos)
{
return handler.newName(name, pos, context);
}
template <typename ParseHandler>
PropertyName*
Parser<ParseHandler>::labelOrIdentifierReference(YieldHandling yieldHandling,
bool yieldTokenizedAsName)
{
PropertyName* ident;
bool isYield;
const Token& tok = tokenStream.currentToken();
if (tok.type == TOK_NAME) {
MOZ_ASSERT(tok.name() != context->names().yield ||
tok.nameContainsEscape() ||
yieldTokenizedAsName,
"tokenizer should have treated unescaped 'yield' as TOK_YIELD");
MOZ_ASSERT_IF(yieldTokenizedAsName, tok.name() == context->names().yield);
ident = tok.name();
isYield = ident == context->names().yield;
} else {
MOZ_ASSERT(tok.type == TOK_YIELD && !yieldTokenizedAsName);
ident = context->names().yield;
isYield = true;
}
if (!isYield) {
if (pc->sc()->strict()) {
const char* badName = ident == context->names().let
? "let"
: ident == context->names().static_
? "static"
: nullptr;
if (badName) {
error(JSMSG_RESERVED_ID, badName);
return nullptr;
}
}
} else {
if (yieldHandling == YieldIsKeyword ||
pc->sc()->strict() ||
versionNumber() >= JSVERSION_1_7)
{
error(JSMSG_RESERVED_ID, "yield");
return nullptr;
}
}
return ident;
}
template <typename ParseHandler>
PropertyName*
Parser<ParseHandler>::bindingIdentifier(YieldHandling yieldHandling)
{
PropertyName* ident;
bool isYield;
const Token& tok = tokenStream.currentToken();
if (tok.type == TOK_NAME) {
MOZ_ASSERT(tok.name() != context->names().yield || tok.nameContainsEscape(),
"tokenizer should have treated unescaped 'yield' as TOK_YIELD");
ident = tok.name();
isYield = ident == context->names().yield;
} else {
MOZ_ASSERT(tok.type == TOK_YIELD);
ident = context->names().yield;
isYield = true;
}
if (!isYield) {
if (pc->sc()->strict()) {
const char* badName = ident == context->names().arguments
? "arguments"
: ident == context->names().eval
? "eval"
: nullptr;
if (badName) {
error(JSMSG_BAD_STRICT_ASSIGN, badName);
return nullptr;
}
badName = ident == context->names().let
? "let"
: ident == context->names().static_
? "static"
: nullptr;
if (badName) {
error(JSMSG_RESERVED_ID, badName);
return nullptr;
}
}
} else {
if (yieldHandling == YieldIsKeyword ||
pc->sc()->strict() ||
versionNumber() >= JSVERSION_1_7)
{
error(JSMSG_RESERVED_ID, "yield");
return nullptr;
}
}
return ident;
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::identifierReference(Handle<PropertyName*> name)
{
Node pn = newName(name);
if (!pn)
return null();
if (!pc->inDestructuringDecl && !noteUsedName(name))
return null();
return pn;
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::stringLiteral()
{
return handler.newStringLiteral(stopStringCompression(), pos());
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::noSubstitutionTemplate()
{
return handler.newTemplateStringLiteral(stopStringCompression(), pos());
}
template <typename ParseHandler>
JSAtom * Parser<ParseHandler>::stopStringCompression() {
JSAtom* atom = tokenStream.currentToken().atom();
// Large strings are fast to parse but slow to compress. Stop compression on
// them, so we don't wait for a long time for compression to finish at the
// end of compilation.
const size_t HUGE_STRING = 50000;
if (sct && sct->active() && atom->length() >= HUGE_STRING)
sct->abort();
return atom;
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::newRegExp()
{
MOZ_ASSERT(!options().selfHostingMode);
// Create the regexp even when doing a syntax parse, to check the regexp's syntax.
const char16_t* chars = tokenStream.getTokenbuf().begin();
size_t length = tokenStream.getTokenbuf().length();
RegExpFlag flags = tokenStream.currentToken().regExpFlags();
Rooted<RegExpObject*> reobj(context);
reobj = RegExpObject::create(context, chars, length, flags, &tokenStream, alloc);
if (!reobj)
return null();
return handler.newRegExp(reobj, pos(), *this);
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::arrayInitializer(YieldHandling yieldHandling, PossibleError* possibleError)
{
MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_LB));
uint32_t begin = pos().begin;
Node literal = handler.newArrayLiteral(begin);
if (!literal)
return null();
TokenKind tt;
if (!tokenStream.getToken(&tt, TokenStream::Operand))
return null();
// Handle an ES6-era array comprehension first.
if (tt == TOK_FOR)
return arrayComprehension(begin);
if (tt == TOK_RB) {
/*
* Mark empty arrays as non-constant, since we cannot easily
* determine their type.
*/
handler.setListFlag(literal, PNX_NONCONST);
} else {
tokenStream.ungetToken();
uint32_t index = 0;
TokenStream::Modifier modifier = TokenStream::Operand;
for (; ; index++) {
if (index >= NativeObject::MAX_DENSE_ELEMENTS_COUNT) {
error(JSMSG_ARRAY_INIT_TOO_BIG);
return null();
}
TokenKind tt;
if (!tokenStream.peekToken(&tt, TokenStream::Operand))
return null();
if (tt == TOK_RB)
break;
if (tt == TOK_COMMA) {
tokenStream.consumeKnownToken(TOK_COMMA, TokenStream::Operand);
if (!handler.addElision(literal, pos()))
return null();
} else if (tt == TOK_TRIPLEDOT) {
tokenStream.consumeKnownToken(TOK_TRIPLEDOT, TokenStream::Operand);
uint32_t begin = pos().begin;
Node inner = assignExpr(InAllowed, yieldHandling, TripledotProhibited,
possibleError);
if (!inner)
return null();
if (!handler.addSpreadElement(literal, begin, inner))
return null();
} else {
Node element = assignExpr(InAllowed, yieldHandling, TripledotProhibited,
possibleError);
if (!element)
return null();
if (foldConstants && !FoldConstants(context, &element, this))
return null();
handler.addArrayElement(literal, element);
}
if (tt != TOK_COMMA) {
/* If we didn't already match TOK_COMMA in above case. */
bool matched;
if (!tokenStream.matchToken(&matched, TOK_COMMA))
return null();
if (!matched) {
modifier = TokenStream::None;
break;
}
if (tt == TOK_TRIPLEDOT && possibleError)
possibleError->setPendingDestructuringErrorAt(pos(), JSMSG_REST_WITH_COMMA);
}
}
MUST_MATCH_TOKEN_MOD(TOK_RB, modifier, JSMSG_BRACKET_AFTER_LIST);
}
handler.setEndPosition(literal, pos().end);
return literal;
}
static JSAtom*
DoubleToAtom(ExclusiveContext* cx, double value)
{
// This is safe because doubles can not be moved.
Value tmp = DoubleValue(value);
return ToAtom<CanGC>(cx, HandleValue::fromMarkedLocation(&tmp));
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::propertyName(YieldHandling yieldHandling, Node propList,
PropertyType* propType, MutableHandleAtom propAtom)
{
TokenKind ltok;
if (!tokenStream.getToken(&ltok, TokenStream::KeywordIsName))
return null();
MOZ_ASSERT(ltok != TOK_RC, "caller should have handled TOK_RC");
bool isGenerator = false;
bool isAsync = false;
if (ltok == TOK_MUL) {
isGenerator = true;
if (!tokenStream.getToken(&ltok, TokenStream::KeywordIsName))
return null();
}
if (ltok == TOK_NAME && tokenStream.currentName() == context->names().async) {
// AsyncMethod[Yield, Await]:
// async [no LineTerminator here] PropertyName[?Yield, ?Await] ...
//
// PropertyName:
// LiteralPropertyName
// ComputedPropertyName[?Yield, ?Await]
//
// LiteralPropertyName:
// IdentifierName
// StringLiteral
// NumericLiteral
//
// ComputedPropertyName[Yield, Await]:
// [ ...
TokenKind tt = TOK_EOF;
if (!tokenStream.peekTokenSameLine(&tt, TokenStream::KeywordIsName))
return null();
if (tt == TOK_STRING || tt == TOK_NUMBER || tt == TOK_LB ||
tt == TOK_NAME || tt == TOK_YIELD)
{
isAsync = true;
tokenStream.consumeKnownToken(tt, TokenStream::KeywordIsName);
ltok = tt;
} else {
tokenStream.addModifierException(TokenStream::NoneIsKeywordIsName);
}
}
if (isAsync && isGenerator) {
error(JSMSG_ASYNC_GENERATOR);
return null();
}
propAtom.set(nullptr);
Node propName;
switch (ltok) {
case TOK_NUMBER:
propAtom.set(DoubleToAtom(context, tokenStream.currentToken().number()));
if (!propAtom.get())
return null();
propName = newNumber(tokenStream.currentToken());
if (!propName)
return null();
break;
case TOK_LB:
propName = computedPropertyName(yieldHandling, propList);
if (!propName)
return null();
break;
case TOK_NAME: {
propAtom.set(tokenStream.currentName());
// Do not look for accessor syntax on generators
if (isGenerator || isAsync ||
!(propAtom.get() == context->names().get ||
propAtom.get() == context->names().set))
{
propName = handler.newObjectLiteralPropertyName(propAtom, pos());
if (!propName)
return null();
break;
}
*propType = propAtom.get() == context->names().get ? PropertyType::Getter
: PropertyType::Setter;
// We have parsed |get| or |set|. Look for an accessor property
// name next.
TokenKind tt;
if (!tokenStream.peekToken(&tt, TokenStream::KeywordIsName))
return null();
if (tt == TOK_NAME) {
if (!checkUnescapedName())
return null();
tokenStream.consumeKnownToken(TOK_NAME, TokenStream::KeywordIsName);
propAtom.set(tokenStream.currentName());
return handler.newObjectLiteralPropertyName(propAtom, pos());
}
if (tt == TOK_STRING) {
if (!checkUnescapedName())
return null();
tokenStream.consumeKnownToken(TOK_STRING, TokenStream::KeywordIsName);
propAtom.set(tokenStream.currentToken().atom());
uint32_t index;
if (propAtom->isIndex(&index)) {
propAtom.set(DoubleToAtom(context, index));
if (!propAtom.get())
return null();
return handler.newNumber(index, NoDecimal, pos());
}
return stringLiteral();
}
if (tt == TOK_NUMBER) {
if (!checkUnescapedName())
return null();
tokenStream.consumeKnownToken(TOK_NUMBER, TokenStream::KeywordIsName);
propAtom.set(DoubleToAtom(context, tokenStream.currentToken().number()));
if (!propAtom.get())
return null();
return newNumber(tokenStream.currentToken());
}
if (tt == TOK_LB) {
if (!checkUnescapedName())
return null();
tokenStream.consumeKnownToken(TOK_LB, TokenStream::KeywordIsName);
return computedPropertyName(yieldHandling, propList);
}
// Not an accessor property after all.
propName = handler.newObjectLiteralPropertyName(propAtom.get(), pos());
if (!propName)
return null();
tokenStream.addModifierException(TokenStream::NoneIsKeywordIsName);
break;
}
case TOK_STRING: {
propAtom.set(tokenStream.currentToken().atom());
uint32_t index;
if (propAtom->isIndex(&index)) {
propName = handler.newNumber(index, NoDecimal, pos());
if (!propName)
return null();
break;
}
propName = stringLiteral();
if (!propName)
return null();
break;
}
default:
error(JSMSG_BAD_PROP_ID);
return null();
}
TokenKind tt;
if (!tokenStream.getToken(&tt))
return null();
if (tt == TOK_COLON) {
if (isGenerator) {
error(JSMSG_BAD_PROP_ID);
return null();
}
*propType = PropertyType::Normal;
return propName;
}
if (ltok == TOK_NAME && (tt == TOK_COMMA || tt == TOK_RC || tt == TOK_ASSIGN)) {
if (isGenerator) {
error(JSMSG_BAD_PROP_ID);
return null();
}
tokenStream.ungetToken();
*propType = tt == TOK_ASSIGN ?
PropertyType::CoverInitializedName :
PropertyType::Shorthand;
return propName;
}
if (tt == TOK_LP) {
tokenStream.ungetToken();
if (isGenerator)
*propType = PropertyType::GeneratorMethod;
else if (isAsync)
*propType = PropertyType::AsyncMethod;
else
*propType = PropertyType::Method;
return propName;
}
error(JSMSG_COLON_AFTER_ID);
return null();
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::computedPropertyName(YieldHandling yieldHandling, Node literal)
{
uint32_t begin = pos().begin;
Node assignNode;
{
// Turn off the inDestructuringDecl flag when parsing computed property
// names. In short, when parsing 'let {[x + y]: z} = obj;', noteUsedName()
// should be called on x and y, but not on z. See the comment on
// Parser<>::checkDestructuringPattern() for details.
AutoClearInDestructuringDecl autoClear(pc);
assignNode = assignExpr(InAllowed, yieldHandling, TripledotProhibited);
if (!assignNode)
return null();
}
MUST_MATCH_TOKEN(TOK_RB, JSMSG_COMP_PROP_UNTERM_EXPR);
Node propname = handler.newComputedName(assignNode, begin, pos().end);
if (!propname)
return null();
handler.setListFlag(literal, PNX_NONCONST);
return propname;
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::objectLiteral(YieldHandling yieldHandling, PossibleError* possibleError)
{
MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_LC));
Node literal = handler.newObjectLiteral(pos().begin);
if (!literal)
return null();
bool seenPrototypeMutation = false;
bool seenCoverInitializedName = false;
RootedAtom propAtom(context);
for (;;) {
TokenKind tt;
if (!tokenStream.getToken(&tt, TokenStream::KeywordIsName))
return null();
if (tt == TOK_RC)
break;
TokenPos namePos = pos();
tokenStream.ungetToken();
PropertyType propType;
Node propName = propertyName(yieldHandling, literal, &propType, &propAtom);
if (!propName)
return null();
if (propType == PropertyType::Normal) {
Node propExpr = assignExpr(InAllowed, yieldHandling, TripledotProhibited,
possibleError);
if (!propExpr)
return null();
handler.checkAndSetIsDirectRHSAnonFunction(propExpr);
if (foldConstants && !FoldConstants(context, &propExpr, this))
return null();
if (propAtom == context->names().proto) {
if (seenPrototypeMutation) {
// Directly report the error when we're not in a
// destructuring context.
if (!possibleError) {
errorAt(namePos.begin, JSMSG_DUPLICATE_PROTO_PROPERTY);
return null();
}
// Otherwise delay error reporting until we've determined
// whether or not we're destructuring.
possibleError->setPendingExpressionErrorAt(namePos,
JSMSG_DUPLICATE_PROTO_PROPERTY);
}
seenPrototypeMutation = true;
// Note: this occurs *only* if we observe TOK_COLON! Only
// __proto__: v mutates [[Prototype]]. Getters, setters,
// method/generator definitions, computed property name
// versions of all of these, and shorthands do not.
if (!handler.addPrototypeMutation(literal, namePos.begin, propExpr))
return null();
} else {
if (!handler.isConstant(propExpr))
handler.setListFlag(literal, PNX_NONCONST);
if (!handler.addPropertyDefinition(literal, propName, propExpr))
return null();
}
} else if (propType == PropertyType::Shorthand) {
/*
* Support, e.g., |var {x, y} = o| as destructuring shorthand
* for |var {x: x, y: y} = o|, and |var o = {x, y}| as initializer
* shorthand for |var o = {x: x, y: y}|.
*/
TokenKind propToken = TOK_NAME;
if (!tokenStream.checkForKeyword(propAtom, &propToken))
return null();
if (propToken != TOK_NAME && propToken != TOK_YIELD) {
error(JSMSG_RESERVED_ID, TokenKindToDesc(propToken));
return null();
}
Rooted<PropertyName*> name(context,
identifierReference(yieldHandling, propToken == TOK_YIELD));
if (!name)
return null();
Node nameExpr = identifierReference(name);
if (!nameExpr)
return null();
if (!handler.addShorthand(literal, propName, nameExpr))
return null();
} else if (propType == PropertyType::CoverInitializedName) {
/*
* Support, e.g., |var {x=1, y=2} = o| as destructuring shorthand
* with default values, as per ES6 12.14.5
*/
TokenKind propToken = TOK_NAME;
if (!tokenStream.checkForKeyword(propAtom, &propToken))
return null();
if (propToken != TOK_NAME && propToken != TOK_YIELD) {
error(JSMSG_RESERVED_ID, TokenKindToDesc(propToken));
return null();
}
Rooted<PropertyName*> name(context,
identifierReference(yieldHandling, propToken == TOK_YIELD));
if (!name)
return null();
Node lhs = identifierReference(name);
if (!lhs)
return null();
tokenStream.consumeKnownToken(TOK_ASSIGN);
if (!seenCoverInitializedName) {
// "shorthand default" or "CoverInitializedName" syntax is only
// valid in the case of destructuring.
seenCoverInitializedName = true;
if (!possibleError) {
// Destructuring defaults are definitely not allowed in this object literal,
// because of something the caller knows about the preceding code.
// For example, maybe the preceding token is an operator: `x + {y=z}`.
error(JSMSG_COLON_AFTER_ID);
return null();
}
// Here we set a pending error so that later in the parse, once we've
// determined whether or not we're destructuring, the error can be
// reported or ignored appropriately.
possibleError->setPendingExpressionErrorAt(pos(), JSMSG_COLON_AFTER_ID);
}
Node rhs;
{
// Clearing `inDestructuringDecl` allows name use to be noted
// in Parser::identifierReference. See bug 1255167.
AutoClearInDestructuringDecl autoClear(pc);
rhs = assignExpr(InAllowed, yieldHandling, TripledotProhibited);
if (!rhs)
return null();
}
handler.checkAndSetIsDirectRHSAnonFunction(rhs);
Node propExpr = handler.newAssignment(PNK_ASSIGN, lhs, rhs, JSOP_NOP);
if (!propExpr)
return null();
if (!handler.addPropertyDefinition(literal, propName, propExpr))
return null();
if (!abortIfSyntaxParser())
return null();
} else {
RootedAtom funName(context);
if (!tokenStream.isCurrentTokenType(TOK_RB)) {
funName = propAtom;
if (propType == PropertyType::Getter || propType == PropertyType::Setter) {
funName = prefixAccessorName(propType, propAtom);
if (!funName)
return null();
}
}
Node fn = methodDefinition(namePos.begin, propType, funName);
if (!fn)
return null();
handler.checkAndSetIsDirectRHSAnonFunction(fn);
JSOp op = JSOpFromPropertyType(propType);
if (!handler.addObjectMethodDefinition(literal, propName, fn, op))
return null();
}
if (!tokenStream.getToken(&tt))
return null();
if (tt == TOK_RC)
break;
if (tt != TOK_COMMA) {
error(JSMSG_CURLY_AFTER_LIST);
return null();
}
}
handler.setEndPosition(literal, pos().end);
return literal;
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::methodDefinition(uint32_t preludeStart, PropertyType propType,
HandleAtom funName)
{
FunctionSyntaxKind kind;
switch (propType) {
case PropertyType::Getter:
kind = Getter;
break;
case PropertyType::GetterNoExpressionClosure:
kind = GetterNoExpressionClosure;
break;
case PropertyType::Setter:
kind = Setter;
break;
case PropertyType::SetterNoExpressionClosure:
kind = SetterNoExpressionClosure;
break;
case PropertyType::Method:
case PropertyType::GeneratorMethod:
case PropertyType::AsyncMethod:
kind = Method;
break;
case PropertyType::Constructor:
kind = ClassConstructor;
break;
case PropertyType::DerivedConstructor:
kind = DerivedClassConstructor;
break;
default:
MOZ_CRASH("Parser: methodDefinition: unexpected property type");
}
GeneratorKind generatorKind = (propType == PropertyType::GeneratorMethod ||
propType == PropertyType::AsyncMethod)
? StarGenerator
: NotGenerator;
FunctionAsyncKind asyncKind = (propType == PropertyType::AsyncMethod)
? AsyncFunction
: SyncFunction;
YieldHandling yieldHandling = GetYieldHandling(generatorKind, asyncKind);
Node pn = handler.newFunctionExpression();
if (!pn)
return null();
return functionDefinition(preludeStart, pn, InAllowed, yieldHandling, funName,
kind, generatorKind, asyncKind);
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::tryNewTarget(Node &newTarget)
{
MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_NEW));
newTarget = null();
Node newHolder = handler.newPosHolder(pos());
if (!newHolder)
return false;
uint32_t begin = pos().begin;
// |new| expects to look for an operand, so we will honor that.
TokenKind next;
if (!tokenStream.getToken(&next, TokenStream::Operand))
return false;
// Don't unget the token, since lookahead cannot handle someone calling
// getToken() with a different modifier. Callers should inspect currentToken().
if (next != TOK_DOT)
return true;
if (!tokenStream.getToken(&next))
return false;
if (next != TOK_NAME || tokenStream.currentName() != context->names().target) {
error(JSMSG_UNEXPECTED_TOKEN, "target", TokenKindToDesc(next));
return false;
}
if (!checkUnescapedName())
return false;
if (!pc->sc()->allowNewTarget()) {
errorAt(begin, JSMSG_BAD_NEWTARGET);
return false;
}
Node targetHolder = handler.newPosHolder(pos());
if (!targetHolder)
return false;
newTarget = handler.newNewTarget(newHolder, targetHolder);
return !!newTarget;
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::primaryExpr(YieldHandling yieldHandling, TripledotHandling tripledotHandling,
TokenKind tt, PossibleError* possibleError,
InvokedPrediction invoked /* = PredictUninvoked */)
{
MOZ_ASSERT(tokenStream.isCurrentTokenType(tt));
JS_CHECK_RECURSION(context, return null());
switch (tt) {
case TOK_FUNCTION:
return functionExpr(pos().begin, invoked);
case TOK_CLASS:
return classDefinition(yieldHandling, ClassExpression, NameRequired);
case TOK_LB:
return arrayInitializer(yieldHandling, possibleError);
case TOK_LC:
return objectLiteral(yieldHandling, possibleError);
case TOK_LP: {
TokenKind next;
if (!tokenStream.peekToken(&next, TokenStream::Operand))
return null();
if (next == TOK_RP) {
// Not valid expression syntax, but this is valid in an arrow function
// with no params: `() => body`.
tokenStream.consumeKnownToken(next, TokenStream::Operand);
if (!tokenStream.peekToken(&next))
return null();
if (next != TOK_ARROW) {
error(JSMSG_UNEXPECTED_TOKEN, "expression", TokenKindToDesc(TOK_RP));
return null();
}
// Now just return something that will allow parsing to continue.
// It doesn't matter what; when we reach the =>, we will rewind and
// reparse the whole arrow function. See Parser::assignExpr.
return handler.newNullLiteral(pos());
}
if (next == TOK_FOR) {
uint32_t begin = pos().begin;
tokenStream.consumeKnownToken(next, TokenStream::Operand);
return generatorComprehension(begin);
}
// Pass |possibleError| to support destructuring in arrow parameters.
Node expr = exprInParens(InAllowed, yieldHandling, TripledotAllowed, possibleError);
if (!expr)
return null();
MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_IN_PAREN);
handler.setEndPosition(expr, pos().end);
return handler.parenthesize(expr);
}
case TOK_TEMPLATE_HEAD:
return templateLiteral(yieldHandling);
case TOK_NO_SUBS_TEMPLATE:
return noSubstitutionTemplate();
case TOK_STRING:
return stringLiteral();
case TOK_YIELD:
case TOK_NAME: {
if (tokenStream.currentName() == context->names().async) {
TokenKind nextSameLine = TOK_EOF;
if (!tokenStream.peekTokenSameLine(&nextSameLine))
return null();
if (nextSameLine == TOK_FUNCTION) {
uint32_t preludeStart = pos().begin;
tokenStream.consumeKnownToken(TOK_FUNCTION);
return functionExpr(preludeStart, PredictUninvoked, AsyncFunction);
}
}
Rooted<PropertyName*> name(context, identifierReference(yieldHandling));
if (!name)
return null();
return identifierReference(name);
}
case TOK_REGEXP:
return newRegExp();
case TOK_NUMBER:
return newNumber(tokenStream.currentToken());
case TOK_TRUE:
return handler.newBooleanLiteral(true, pos());
case TOK_FALSE:
return handler.newBooleanLiteral(false, pos());
case TOK_THIS: {
if (pc->isFunctionBox())
pc->functionBox()->usesThis = true;
Node thisName = null();
if (pc->sc()->thisBinding() == ThisBinding::Function) {
thisName = newThisName();
if (!thisName)
return null();
}
return handler.newThisLiteral(pos(), thisName);
}
case TOK_NULL:
return handler.newNullLiteral(pos());
case TOK_TRIPLEDOT: {
// This isn't valid expression syntax, but it's valid in an arrow
// function as a trailing rest param: `(a, b, ...rest) => body`. Check
// if it's directly under
// CoverParenthesizedExpressionAndArrowParameterList, and check for a
// name, closing parenthesis, and arrow, and allow it only if all are
// present.
if (tripledotHandling != TripledotAllowed) {
error(JSMSG_UNEXPECTED_TOKEN, "expression", TokenKindToDesc(tt));
return null();
}
TokenKind next;
if (!tokenStream.getToken(&next))
return null();
if (next == TOK_LB || next == TOK_LC) {
// Validate, but don't store the pattern right now. The whole arrow
// function is reparsed in functionFormalParametersAndBody().
if (!destructuringDeclaration(DeclarationKind::CoverArrowParameter, yieldHandling,
next))
{
return null();
}
} else {
// This doesn't check that the provided name is allowed, e.g. if
// the enclosing code is strict mode code, any of "let", "yield",
// or "arguments" should be prohibited. Argument-parsing code
// handles that.
if (next != TOK_NAME && next != TOK_YIELD) {
error(JSMSG_UNEXPECTED_TOKEN, "rest argument name", TokenKindToDesc(next));
return null();
}
}
if (!tokenStream.getToken(&next))
return null();
if (next != TOK_RP) {
error(JSMSG_UNEXPECTED_TOKEN, "closing parenthesis", TokenKindToDesc(next));
return null();
}
if (!tokenStream.peekToken(&next))
return null();
if (next != TOK_ARROW) {
// Advance the scanner for proper error location reporting.
tokenStream.consumeKnownToken(next);
error(JSMSG_UNEXPECTED_TOKEN, "'=>' after argument list", TokenKindToDesc(next));
return null();
}
tokenStream.ungetToken(); // put back right paren
// Return an arbitrary expression node. See case TOK_RP above.
return handler.newNullLiteral(pos());
}
default:
error(JSMSG_UNEXPECTED_TOKEN, "expression", TokenKindToDesc(tt));
return null();
}
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::exprInParens(InHandling inHandling, YieldHandling yieldHandling,
TripledotHandling tripledotHandling,
PossibleError* possibleError /* = nullptr */)
{
MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_LP));
return expr(inHandling, yieldHandling, tripledotHandling, possibleError, PredictInvoked);
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::warnOnceAboutExprClosure()
{
#ifndef RELEASE_OR_BETA
JSContext* cx = context->maybeJSContext();
if (!cx)
return true;
if (!cx->compartment()->warnedAboutExprClosure) {
if (!warning(JSMSG_DEPRECATED_EXPR_CLOSURE))
return false;
cx->compartment()->warnedAboutExprClosure = true;
}
#endif
return true;
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::warnOnceAboutForEach()
{
JSContext* cx = context->maybeJSContext();
if (!cx)
return true;
if (!cx->compartment()->warnedAboutForEach) {
// Disabled warning spew.
// if (!warning(JSMSG_DEPRECATED_FOR_EACH))
// return false;
cx->compartment()->warnedAboutForEach = true;
}
return true;
}
template class Parser<FullParseHandler>;
template class Parser<SyntaxParseHandler>;
} /* namespace frontend */
} /* namespace js */