blob: 4e4aa7130709d87b7f87c20149806942c08694cb [file] [log] [blame]
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sts=4 et sw=4 tw=99:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*
* JS parser.
*
* This is a recursive-descent parser for the JavaScript language specified by
* "The JavaScript 1.5 Language Specification". 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-inl.h"
#include "jsapi.h"
#include "jsatom.h"
#include "jscntxt.h"
#include "jsfun.h"
#include "jsobj.h"
#include "jsopcode.h"
#include "jsscript.h"
#include "jstypes.h"
#include "asmjs/AsmJSValidate.h"
#include "builtin/ModuleObject.h"
#include "frontend/BytecodeCompiler.h"
#include "frontend/FoldConstants.h"
#include "frontend/ParseMaps.h"
#include "frontend/TokenStream.h"
#include "vm/Shape.h"
#include "jsatominlines.h"
#include "jsscriptinlines.h"
#include "frontend/ParseNode-inl.h"
#include "vm/ScopeObject-inl.h"
using namespace js;
using namespace js::gc;
using mozilla::Maybe;
using JS::AutoGCRooter;
JSFunction::AutoParseUsingFunctionBox::AutoParseUsingFunctionBox(ExclusiveContext* cx,
frontend::FunctionBox* funbox)
: fun_(cx, funbox->function()),
oldEnv_(cx, fun_->environment())
{
fun_->unsetEnvironment();
fun_->setFunctionBox(funbox);
funbox->computeAllowSyntax(fun_);
funbox->computeInWith(fun_);
funbox->computeThisBinding(fun_);
}
JSFunction::AutoParseUsingFunctionBox::~AutoParseUsingFunctionBox()
{
fun_->unsetFunctionBox();
fun_->initEnvironment(oldEnv_);
}
namespace js {
namespace frontend {
typedef Rooted<StaticBlockObject*> RootedStaticBlockObject;
typedef Handle<StaticBlockObject*> HandleStaticBlockObject;
typedef Rooted<NestedScopeObject*> RootedNestedScopeObject;
typedef Handle<NestedScopeObject*> HandleNestedScopeObject;
/* Read a token. Report an error and return null() if that token isn't of type tt. */
#define MUST_MATCH_TOKEN_MOD(tt, modifier, errno) \
JS_BEGIN_MACRO \
TokenKind token; \
if (!tokenStream.getToken(&token, modifier)) \
return null(); \
if (token != tt) { \
report(ParseError, false, null(), errno); \
return null(); \
} \
JS_END_MACRO
#define MUST_MATCH_TOKEN(tt, errno) MUST_MATCH_TOKEN_MOD(tt, TokenStream::None, errno)
template <>
bool
ParseContext<FullParseHandler>::checkLocalsOverflow(TokenStream& ts)
{
if (vars_.length() + bodyLevelLexicals_.length() >= LOCALNO_LIMIT) {
ts.reportError(JSMSG_TOO_MANY_LOCALS);
return false;
}
return true;
}
static void
MarkUsesAsHoistedLexical(ParseNode* pn)
{
MOZ_ASSERT(pn->isDefn());
Definition* dn = (Definition*)pn;
ParseNode** pnup = &dn->dn_uses;
ParseNode* pnu;
unsigned start = pn->pn_blockid;
// In ES6, lexical bindings cannot be accessed until initialized.
// Distinguish hoisted uses as a different JSOp for easier compilation.
while ((pnu = *pnup) != nullptr && pnu->pn_blockid >= start) {
MOZ_ASSERT(pnu->isUsed());
pnu->pn_dflags |= PND_LEXICAL;
pnup = &pnu->pn_link;
}
}
void
SharedContext::computeAllowSyntax(JSObject* staticScope)
{
for (StaticScopeIter<CanGC> it(context, staticScope); !it.done(); it++) {
if (it.type() == StaticScopeIter<CanGC>::Function && !it.fun().isArrow()) {
// Any function supports new.target.
allowNewTarget_ = true;
allowSuperProperty_ = it.fun().allowSuperProperty();
if (it.maybeFunctionBox()) {
superScopeAlreadyNeedsHomeObject_ = it.maybeFunctionBox()->needsHomeObject();
allowSuperCall_ = it.maybeFunctionBox()->isDerivedClassConstructor();
} else {
allowSuperCall_ = it.fun().isDerivedClassConstructor();
}
break;
}
}
}
void
SharedContext::computeThisBinding(JSObject* staticScope)
{
for (StaticScopeIter<CanGC> it(context, staticScope); !it.done(); it++) {
if (it.type() == StaticScopeIter<CanGC>::Module) {
thisBinding_ = ThisBinding::Module;
return;
}
if (it.type() == StaticScopeIter<CanGC>::Function) {
// Arrow functions and generator expression lambdas don't have
// their own `this` binding.
if (it.fun().isArrow())
continue;
bool isDerived;
if (it.maybeFunctionBox()) {
if (it.maybeFunctionBox()->inGenexpLambda)
continue;
isDerived = it.maybeFunctionBox()->isDerivedClassConstructor();
} else {
if (it.fun().nonLazyScript()->isGeneratorExp())
continue;
isDerived = it.fun().isDerivedClassConstructor();
}
// Derived class constructors (including nested arrow functions and
// eval) need TDZ checks when accessing |this|.
if (isDerived)
needsThisTDZChecks_ = true;
thisBinding_ = ThisBinding::Function;
return;
}
}
thisBinding_ = ThisBinding::Global;
}
void
SharedContext::computeInWith(JSObject* staticScope)
{
for (StaticScopeIter<CanGC> it(context, staticScope); !it.done(); it++) {
if (it.type() == StaticScopeIter<CanGC>::With) {
inWith_ = true;
break;
}
}
}
void
SharedContext::markSuperScopeNeedsHomeObject()
{
MOZ_ASSERT(allowSuperProperty());
if (superScopeAlreadyNeedsHomeObject_)
return;
for (StaticScopeIter<CanGC> it(context, staticScope()); !it.done(); it++) {
if (it.type() == StaticScopeIter<CanGC>::Function && !it.fun().isArrow()) {
MOZ_ASSERT(it.fun().allowSuperProperty());
// If we are still emitting the outer function that needs a home
// object, mark it as needing one. Otherwise, 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.
if (it.maybeFunctionBox())
it.maybeFunctionBox()->setNeedsHomeObject();
else
MOZ_ASSERT(it.fun().nonLazyScript()->needsHomeObject());
superScopeAlreadyNeedsHomeObject_ = true;
return;
}
}
MOZ_CRASH("Must have found an enclosing function box scope that allows super.property");
}
// See comment on member function declaration.
template <>
bool
ParseContext<FullParseHandler>::define(TokenStream& ts,
HandlePropertyName name, ParseNode* pn, Definition::Kind kind)
{
MOZ_ASSERT(!pn->isUsed());
MOZ_ASSERT_IF(pn->isDefn(), pn->isPlaceholder());
Definition* prevDef = nullptr;
if (kind == Definition::LET || kind == Definition::CONSTANT)
prevDef = decls_.lookupFirst(name);
else
MOZ_ASSERT(!decls_.lookupFirst(name));
if (!prevDef)
prevDef = lexdeps.lookupDefn<FullParseHandler>(name);
if (prevDef) {
ParseNode** pnup = &prevDef->dn_uses;
ParseNode* pnu;
unsigned start = (kind == Definition::LET || kind == Definition::CONSTANT)
? pn->pn_blockid : bodyid;
while ((pnu = *pnup) != nullptr && pnu->pn_blockid >= start) {
MOZ_ASSERT(pnu->pn_blockid >= bodyid);
MOZ_ASSERT(pnu->isUsed());
pnu->pn_lexdef = (Definition*) pn;
pn->pn_dflags |= pnu->pn_dflags & PND_USE2DEF_FLAGS;
pnup = &pnu->pn_link;
}
if (!pnu || pnu != prevDef->dn_uses) {
*pnup = pn->dn_uses;
pn->dn_uses = prevDef->dn_uses;
prevDef->dn_uses = pnu;
if (!pnu && prevDef->isPlaceholder())
lexdeps->remove(name);
}
pn->pn_dflags |= prevDef->pn_dflags & PND_CLOSED;
}
MOZ_ASSERT_IF(kind != Definition::LET && kind != Definition::CONSTANT, !lexdeps->lookup(name));
pn->setDefn(true);
pn->pn_dflags &= ~PND_PLACEHOLDER;
if (kind == Definition::CONSTANT)
pn->pn_dflags |= PND_CONST;
Definition* dn = (Definition*)pn;
switch (kind) {
case Definition::ARG:
MOZ_ASSERT(sc->isFunctionBox());
dn->setOp((CodeSpec[dn->getOp()].format & JOF_SET) ? JSOP_SETARG : JSOP_GETARG);
dn->pn_blockid = bodyid;
dn->pn_dflags |= PND_BOUND;
if (!dn->pn_scopecoord.setSlot(ts, args_.length()))
return false;
if (!args_.append(dn))
return false;
if (args_.length() >= ARGNO_LIMIT) {
ts.reportError(JSMSG_TOO_MANY_FUN_ARGS);
return false;
}
if (name == ts.names().empty)
break;
if (!decls_.addUnique(name, dn))
return false;
break;
case Definition::VAR:
// Unlike args, var bindings keep the blockid of where the statement
// was found until ParseContext::generateBindings. In practice, this
// means when we emit bytecode for function scripts, var Definition
// nodes will have their static scopes correctly set to the static
// scope of the body. For global scripts, vars are dynamically defined
// on the global object and their static scope is never consulted.
if (!vars_.append(dn))
return false;
// We always track vars for redeclaration checks, but only non-global
// and non-deoptimized (e.g., inside a with scope) vars live in frame
// or CallObject slots.
if (!sc->isGlobalContext() && !dn->isDeoptimized()) {
dn->setOp((CodeSpec[dn->getOp()].format & JOF_SET) ? JSOP_SETLOCAL : JSOP_GETLOCAL);
dn->pn_dflags |= PND_BOUND;
if (!dn->pn_scopecoord.setSlot(ts, vars_.length() - 1))
return false;
if (!checkLocalsOverflow(ts))
return false;
}
if (atModuleScope())
dn->pn_dflags |= PND_CLOSED;
if (!decls_.addUnique(name, dn))
return false;
break;
case Definition::LET:
case Definition::CONSTANT:
// See FullParseHandler::setLexicalDeclarationOp.
dn->setOp(dn->pn_scopecoord.isFree() ? JSOP_INITGLEXICAL : JSOP_INITLEXICAL);
dn->pn_dflags |= (PND_LEXICAL | PND_BOUND);
if (atModuleLevel())
dn->pn_dflags |= PND_CLOSED;
if (atBodyLevel()) {
if (!bodyLevelLexicals_.append(dn))
return false;
if (!checkLocalsOverflow(ts))
return false;
}
// In ES6, lexical bindings cannot be accessed until initialized. If
// the definition has existing uses, they need to be marked so that we
// emit dead zone checks.
MarkUsesAsHoistedLexical(pn);
if (!decls_.addShadow(name, dn))
return false;
break;
case Definition::IMPORT:
dn->pn_dflags |= PND_LEXICAL | PND_CLOSED;
MOZ_ASSERT(atBodyLevel());
if (!decls_.addShadow(name, dn))
return false;
break;
default:
MOZ_CRASH("unexpected kind");
}
return true;
}
template <>
bool
ParseContext<SyntaxParseHandler>::checkLocalsOverflow(TokenStream& ts)
{
return true;
}
template <>
bool
ParseContext<SyntaxParseHandler>::define(TokenStream& ts, HandlePropertyName name, Node pn,
Definition::Kind kind)
{
MOZ_ASSERT(!decls_.lookupFirst(name));
if (lexdeps.lookupDefn<SyntaxParseHandler>(name))
lexdeps->remove(name);
// Keep track of the number of arguments in args_, for fun->nargs.
if (kind == Definition::ARG) {
if (!args_.append((Definition*) nullptr))
return false;
if (args_.length() >= ARGNO_LIMIT) {
ts.reportError(JSMSG_TOO_MANY_FUN_ARGS);
return false;
}
}
return decls_.addUnique(name, kind);
}
template <typename ParseHandler>
void
ParseContext<ParseHandler>::prepareToAddDuplicateArg(HandlePropertyName name, DefinitionNode prevDecl)
{
MOZ_ASSERT(decls_.lookupFirst(name) == prevDecl);
decls_.remove(name);
}
template <typename ParseHandler>
void
ParseContext<ParseHandler>::updateDecl(TokenStream& ts, JSAtom* atom, Node pn)
{
Definition* oldDecl = decls_.lookupFirst(atom);
pn->setDefn(true);
Definition* newDecl = (Definition*)pn;
decls_.updateFirst(atom, newDecl);
if (sc->isGlobalContext() || oldDecl->isDeoptimized()) {
MOZ_ASSERT(newDecl->isFreeVar());
// Global 'var' bindings have no slots, but are still tracked for
// redeclaration checks.
for (uint32_t i = 0; i < vars_.length(); i++) {
if (vars_[i] == oldDecl) {
// Terribly, deoptimized bindings may be updated with
// optimized bindings due to hoisted function statements, so
// give the new declaration a slot.
//
// Global bindings are excluded as currently they are never
// frame slots. The notion of being deoptimized is not
// applicable to them.
if (oldDecl->isDeoptimized() && !newDecl->isDeoptimized() &&
!sc->isGlobalContext())
{
newDecl->pn_dflags |= PND_BOUND;
newDecl->pn_scopecoord.setSlot(ts, i);
newDecl->setOp(JSOP_GETLOCAL);
}
vars_[i] = newDecl;
break;
}
}
return;
}
MOZ_ASSERT(oldDecl->isBound());
MOZ_ASSERT(!oldDecl->pn_scopecoord.isFree());
newDecl->pn_scopecoord = oldDecl->pn_scopecoord;
newDecl->pn_dflags |= PND_BOUND;
if (IsArgOp(oldDecl->getOp())) {
newDecl->setOp(JSOP_GETARG);
MOZ_ASSERT(args_[oldDecl->pn_scopecoord.slot()] == oldDecl);
args_[oldDecl->pn_scopecoord.slot()] = newDecl;
} else {
MOZ_ASSERT(IsLocalOp(oldDecl->getOp()));
newDecl->setOp(JSOP_GETLOCAL);
MOZ_ASSERT(vars_[oldDecl->pn_scopecoord.slot()] == oldDecl);
vars_[oldDecl->pn_scopecoord.slot()] = newDecl;
}
}
template <typename ParseHandler>
void
ParseContext<ParseHandler>::popLetDecl(JSAtom* atom)
{
MOZ_ASSERT(ParseHandler::getDefinitionKind(decls_.lookupFirst(atom)) == Definition::LET ||
ParseHandler::getDefinitionKind(decls_.lookupFirst(atom)) == Definition::CONSTANT);
decls_.remove(atom);
}
template <typename ParseHandler>
static void
AppendPackedBindings(const ParseContext<ParseHandler>* pc, const DeclVector& vec, Binding* dst,
uint32_t* numUnaliased = nullptr)
{
for (size_t i = 0; i < vec.length(); ++i, ++dst) {
Definition* dn = vec[i];
PropertyName* name = dn->name();
Binding::Kind kind;
switch (dn->kind()) {
case Definition::LET:
// Treat body-level let declarations as var bindings by falling
// through. The fact that the binding is in fact a let declaration
// is reflected in the slot. All body-level lets go after the
// vars.
case Definition::VAR:
kind = Binding::VARIABLE;
break;
case Definition::CONSTANT:
kind = Binding::CONSTANT;
break;
case Definition::ARG:
kind = Binding::ARGUMENT;
break;
case Definition::IMPORT:
// Skip module imports.
continue;
default:
MOZ_CRASH("unexpected dn->kind");
}
bool aliased;
if (pc->sc->isGlobalContext()) {
// Bindings for global and eval scripts are used solely for redeclaration
// checks in the prologue. Neither 'true' nor 'false' accurately describe
// their aliased-ness. These bindings don't live in CallObjects or the
// frame, but either on the global object and the global lexical
// scope. Force aliased to be false to avoid confusing other analyses in
// the engine that assumes the frame has a call object if there are
// aliased bindings.
aliased = false;
} else {
/*
* Bindings::init does not check for duplicates so we must ensure that
* only one binding with a given name is marked aliased. pc->decls
* maintains the canonical definition for each name, so use that.
*/
MOZ_ASSERT_IF(dn->isClosed(), pc->decls().lookupFirst(name) == dn);
aliased = dn->isClosed() ||
(pc->sc->allLocalsAliased() &&
pc->decls().lookupFirst(name) == dn);
}
*dst = Binding(name, kind, aliased);
if (!aliased && numUnaliased)
++*numUnaliased;
}
}
template <typename ParseHandler>
bool
ParseContext<ParseHandler>::generateBindings(ExclusiveContext* cx, TokenStream& ts, LifoAlloc& alloc,
MutableHandle<Bindings> bindings) const
{
MOZ_ASSERT_IF(sc->isFunctionBox(), args_.length() < ARGNO_LIMIT);
MOZ_ASSERT_IF(sc->isModuleBox(), args_.length() == 0);
MOZ_ASSERT(vars_.length() + bodyLevelLexicals_.length() < LOCALNO_LIMIT);
/*
* Avoid pathological edge cases by explicitly limiting the total number of
* bindings to what will fit in a uint32_t.
*/
if (UINT32_MAX - args_.length() <= vars_.length() + bodyLevelLexicals_.length())
return ts.reportError(JSMSG_TOO_MANY_LOCALS);
// Fix up slots in non-global contexts. In global contexts all body-level
// names are dynamically defined and do not live in either frame or
// CallObject slots.
if (!sc->isGlobalContext()) {
// Fix up the blockids of vars, whose static scope is always at the body
// level. This could not be done up front in ParseContext::define, as
// the original blockids are used for redeclaration checks.
for (size_t i = 0; i < vars_.length(); i++)
vars_[i]->pn_blockid = bodyid;
// Fix up the slots of body-level lets to come after the vars now that we
// know how many vars there are.
for (size_t i = 0; i < bodyLevelLexicals_.length(); i++) {
Definition* dn = bodyLevelLexicals_[i];
if (!dn->pn_scopecoord.setSlot(ts, vars_.length() + i))
return false;
}
}
uint32_t count = args_.length() + vars_.length() + bodyLevelLexicals_.length();
Binding* packedBindings = alloc.newArrayUninitialized<Binding>(count);
if (!packedBindings) {
ReportOutOfMemory(cx);
return false;
}
uint32_t numUnaliasedVars = 0;
uint32_t numUnaliasedBodyLevelLexicals = 0;
AppendPackedBindings(this, args_, packedBindings);
AppendPackedBindings(this, vars_, packedBindings + args_.length(), &numUnaliasedVars);
AppendPackedBindings(this, bodyLevelLexicals_,
packedBindings + args_.length() + vars_.length(), &numUnaliasedBodyLevelLexicals);
return Bindings::initWithTemporaryStorage(cx, bindings, args_.length(), vars_.length(),
bodyLevelLexicals_.length(), blockScopeDepth,
numUnaliasedVars, numUnaliasedBodyLevelLexicals,
packedBindings, sc->isModuleBox());
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::reportHelper(ParseReportKind kind, bool strict, uint32_t offset,
unsigned errorNumber, va_list args)
{
bool result = false;
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.reportStrictWarningErrorNumberVA(offset, errorNumber, args);
break;
case ParseStrictError:
result = tokenStream.reportStrictModeErrorNumberVA(offset, strict, errorNumber, args);
break;
}
return result;
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::report(ParseReportKind kind, bool strict, Node pn, unsigned errorNumber, ...)
{
uint32_t offset = (pn ? handler.getPosition(pn) : pos()).begin;
va_list args;
va_start(args, errorNumber);
bool result = reportHelper(kind, strict, offset, errorNumber, args);
va_end(args);
return result;
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::reportNoOffset(ParseReportKind kind, bool strict, unsigned errorNumber, ...)
{
va_list args;
va_start(args, errorNumber);
bool result = reportHelper(kind, strict, TokenStream::NoOffset, errorNumber, args);
va_end(args);
return result;
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::reportWithOffset(ParseReportKind kind, bool strict, uint32_t offset,
unsigned errorNumber, ...)
{
va_list args;
va_start(args, errorNumber);
bool result = reportHelper(kind, strict, offset, errorNumber, args);
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,
Parser<SyntaxParseHandler>* syntaxParser,
LazyScript* lazyOuterFunction)
: AutoGCRooter(cx, PARSER),
context(cx),
alloc(*alloc),
tokenStream(cx, options, chars, length, thisForCtor()),
traceListHead(nullptr),
pc(nullptr),
blockScopes(cx),
sct(nullptr),
ss(nullptr),
keepAtoms(cx->perThreadData),
foldConstants(foldConstants),
#ifdef DEBUG
checkOptionsCalled(false),
#endif
abortedSyntaxParse(false),
isUnexpectedEOF_(false),
handler(cx, *alloc, tokenStream, syntaxParser, lazyOuterFunction)
{
{
AutoLockForExclusiveAccess lock(cx);
cx->perThreadData->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();
{
AutoLockForExclusiveAccess lock(context);
context->perThreadData->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::FunctionBox(ExclusiveContext* cx, ObjectBox* traceListHead, JSFunction* fun,
JSObject* enclosingStaticScope, ParseContext<ParseHandler>* outerpc,
Directives directives, bool extraWarnings, GeneratorKind generatorKind)
: ObjectBox(fun, traceListHead),
SharedContext(cx, directives, extraWarnings),
bindings(),
enclosingStaticScope_(enclosingStaticScope),
bufStart(0),
bufEnd(0),
startLine(1),
startColumn(0),
length(0),
generatorKindBits_(GeneratorKindAsBits(generatorKind)),
inGenexpLambda(false),
hasDestructuringArgs(false),
useAsm(false),
insideUseAsm(outerpc && outerpc->useAsmOrInsideUseAsm()),
wasEmitted(false),
usesArguments(false),
usesApply(false),
usesThis(false),
usesReturn(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());
}
template <typename ParseHandler>
FunctionBox*
Parser<ParseHandler>::newFunctionBox(Node fn, JSFunction* fun,
ParseContext<ParseHandler>* outerpc,
Directives inheritedDirectives,
GeneratorKind generatorKind,
JSObject* enclosingStaticScope)
{
MOZ_ASSERT_IF(outerpc, enclosingStaticScope == outerpc->innermostStaticScope());
MOZ_ASSERT(fun);
/*
* 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, traceListHead, fun, enclosingStaticScope, outerpc,
inheritedDirectives, options().extraWarningsOption,
generatorKind);
if (!funbox) {
ReportOutOfMemory(context);
return nullptr;
}
traceListHead = funbox;
if (fn)
handler.setFunctionBox(fn, funbox);
return funbox;
}
template <typename ParseHandler>
ModuleBox::ModuleBox(ExclusiveContext* cx, ObjectBox* traceListHead, ModuleObject* module,
ParseContext<ParseHandler>* outerpc)
: ObjectBox(module, traceListHead),
SharedContext(cx, Directives(true), false),
bindings(),
exportNames(cx)
{
computeThisBinding(staticScope());
}
template <typename ParseHandler>
ModuleBox*
Parser<ParseHandler>::newModuleBox(Node pn, HandleModuleObject module)
{
MOZ_ASSERT(module);
/*
* 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 module.
*/
ParseContext<ParseHandler>* outerpc = nullptr;
ModuleBox* modbox =
alloc.new_<ModuleBox>(context, traceListHead, module, outerpc);
if (!modbox) {
ReportOutOfMemory(context);
return nullptr;
}
traceListHead = modbox;
if (pn)
handler.setModuleBox(pn, modbox);
return modbox;
}
template <>
ModuleBox*
Parser<SyntaxParseHandler>::newModuleBox(Node pn, HandleModuleObject module)
{
MOZ_ALWAYS_FALSE(abortIfSyntaxParser());
return nullptr;
}
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);
/*
* Protect atoms from being collected by a GC activation, which might
* - nest on this thread due to out of memory (the so-called "last ditch"
* GC attempted within js_NewGCThing), or
* - run for any reason on another thread if this thread is suspended on
* an object lock before it finishes generating bytecode into a script
* protected from the GC by a root or a stack frame reference.
*/
Rooted<ScopeObject*> staticLexical(context, &context->global()->lexicalScope().staticBlock());
Directives directives(options().strictOption);
GlobalSharedContext globalsc(context, staticLexical, directives,
options().extraWarningsOption);
ParseContext<ParseHandler> globalpc(this, /* parent = */ nullptr, ParseHandler::null(),
&globalsc, /* newDirectives = */ nullptr);
if (!globalpc.init(*this))
return null();
Node pn = statements(YieldIsName);
if (pn) {
TokenKind tt;
if (!tokenStream.getToken(&tt, TokenStream::Operand))
return null();
if (tt != TOK_EOF) {
report(ParseError, false, null(), JSMSG_GARBAGE_AFTER_INPUT,
"script", TokenKindToDesc(tt));
return null();
}
if (foldConstants) {
if (!FoldConstants(context, &pn, this))
return null();
}
}
return pn;
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::reportBadReturn(Node pn, ParseReportKind kind,
unsigned errnum, unsigned anonerrnum)
{
JSAutoByteString name;
JSAtom* atom = pc->sc->asFunctionBox()->function()->atom();
if (atom) {
if (!AtomToPrintableString(context, atom, &name))
return false;
} else {
errnum = anonerrnum;
}
return report(kind, pc->sc->strict(), pn, errnum, name.ptr());
}
/*
* Check that it is permitted to introduce a binding for atom. Strict mode
* forbids introducing new definitions for 'eval', 'arguments', or for any
* strict mode reserved keyword. Use pn for reporting error locations, or use
* pc's token stream if pn is nullptr.
*/
template <typename ParseHandler>
bool
Parser<ParseHandler>::checkStrictBinding(PropertyName* name, Node pn)
{
if (!pc->sc->needStrictChecks())
return true;
if (name == context->names().eval || name == context->names().arguments || IsKeyword(name)) {
JSAutoByteString bytes;
if (!AtomToPrintableString(context, name, &bytes))
return false;
return report(ParseStrictError, pc->sc->strict(), pn,
JSMSG_BAD_BINDING, bytes.ptr());
}
return true;
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::standaloneModule(HandleModuleObject module)
{
MOZ_ASSERT(checkOptionsCalled);
Node mn = handler.newModule();
if (!mn)
return null();
ModuleBox* modulebox = newModuleBox(mn, module);
if (!modulebox)
return null();
handler.setModuleBox(mn, modulebox);
ParseContext<FullParseHandler> modulepc(this, pc, mn, modulebox, nullptr);
if (!modulepc.init(*this))
return null();
ParseNode* pn = statements(YieldIsKeyword);
if (!pn)
return null();
pn->pn_blockid = modulepc.blockid();
MOZ_ASSERT(pn->isKind(PNK_STATEMENTLIST));
mn->pn_body = pn;
TokenKind tt;
if (!tokenStream.getToken(&tt, TokenStream::Operand))
return null();
if (tt != TOK_EOF) {
report(ParseError, false, null(), JSMSG_GARBAGE_AFTER_INPUT, "module", TokenKindToDesc(tt));
return null();
}
if (!FoldConstants(context, &pn, this))
return null();
Rooted<Bindings> bindings(context, modulebox->bindings);
if (!modulepc.generateBindings(context, tokenStream, alloc, &bindings))
return null();
modulebox->bindings = bindings;
MOZ_ASSERT(mn->pn_modulebox == modulebox);
return mn;
}
template <>
SyntaxParseHandler::Node
Parser<SyntaxParseHandler>::standaloneModule(HandleModuleObject module)
{
MOZ_ALWAYS_FALSE(abortIfSyntaxParser());
return SyntaxParseHandler::NodeFailure;
}
template <>
bool
Parser<FullParseHandler>::checkStatementsEOF()
{
// This is designed to be paired with parsing a statement list at the top
// level.
//
// The statements() 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) {
report(ParseError, false, null(), JSMSG_UNEXPECTED_TOKEN,
"expression", TokenKindToDesc(tt));
return false;
}
return true;
}
template <>
ParseNode*
Parser<FullParseHandler>::evalBody()
{
AutoPushStmtInfoPC stmtInfo(*this, StmtType::BLOCK);
ParseNode* block = pushLexicalScope(stmtInfo);
if (!block)
return nullptr;
// For parsing declarations and directives, eval scripts must be
// considered body level despite having a lexical scope.
MOZ_ASSERT(pc->atBodyLevel());
ParseNode* body = statements(YieldIsName);
if (!body)
return nullptr;
if (!checkStatementsEOF())
return nullptr;
block->pn_expr = body;
block->pn_pos = body->pn_pos;
return block;
}
template <>
ParseNode*
Parser<FullParseHandler>::globalBody()
{
MOZ_ASSERT(pc->atGlobalLevel());
ParseNode* body = statements(YieldIsName);
if (!body)
return nullptr;
if (!checkStatementsEOF())
return nullptr;
return body;
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::newThisName()
{
Node thisName = newName(context->names().dotThis);
if (!thisName)
return null();
if (!noteNameUse(context->names().dotThis, thisName))
return null();
return thisName;
}
template <>
bool
Parser<FullParseHandler>::defineFunctionThis()
{
HandlePropertyName dotThis = context->names().dotThis;
// Create a declaration for '.this' if there are any unbound uses in the
// function body.
for (AtomDefnRange r = pc->lexdeps->all(); !r.empty(); r.popFront()) {
if (r.front().key() == dotThis) {
Definition* dn = r.front().value().get<FullParseHandler>();
MOZ_ASSERT(dn->isPlaceholder());
pc->sc->asFunctionBox()->setHasThisBinding();
return pc->define(tokenStream, dotThis, dn, Definition::VAR);
}
}
// Also define a this-binding if direct eval is used, in derived class
// constructors (JSOP_CHECKRETURN relies on it) or if there's a debugger
// statement.
if (pc->sc->hasDirectEval() ||
pc->sc->asFunctionBox()->isDerivedClassConstructor() ||
pc->sc->hasDebuggerStatement())
{
ParseNode* pn = newName(dotThis);
if (!pn)
return false;
pc->sc->asFunctionBox()->setHasThisBinding();
return pc->define(tokenStream, dotThis, pn, Definition::VAR);
}
return true;
}
template <>
bool
Parser<SyntaxParseHandler>::defineFunctionThis()
{
return true;
}
template <>
ParseNode*
Parser<FullParseHandler>::standaloneFunctionBody(HandleFunction fun,
Handle<PropertyNameVector> formals,
GeneratorKind generatorKind,
Directives inheritedDirectives,
Directives* newDirectives,
HandleObject enclosingStaticScope)
{
MOZ_ASSERT(checkOptionsCalled);
Node fn = handler.newFunctionDefinition();
if (!fn)
return null();
ParseNode* argsbody = handler.newList(PNK_ARGSBODY);
if (!argsbody)
return null();
fn->pn_body = argsbody;
FunctionBox* funbox = newFunctionBox(fn, fun, inheritedDirectives, generatorKind,
enclosingStaticScope);
if (!funbox)
return null();
funbox->length = fun->nargs() - fun->hasRest();
handler.setFunctionBox(fn, funbox);
ParseContext<FullParseHandler> funpc(this, pc, fn, funbox, newDirectives);
if (!funpc.init(*this))
return null();
for (unsigned i = 0; i < formals.length(); i++) {
if (!defineArg(fn, formals[i]))
return null();
}
YieldHandling yieldHandling = generatorKind != NotGenerator ? YieldIsKeyword : YieldIsName;
ParseNode* pn = functionBody(InAllowed, yieldHandling, Statement, StatementListBody);
if (!pn)
return null();
TokenKind tt;
if (!tokenStream.getToken(&tt, TokenStream::Operand))
return null();
if (tt != TOK_EOF) {
report(ParseError, false, null(), JSMSG_GARBAGE_AFTER_INPUT,
"function body", TokenKindToDesc(tt));
return null();
}
if (!FoldConstants(context, &pn, this))
return null();
fn->pn_pos.end = pos().end;
MOZ_ASSERT(fn->pn_body->isKind(PNK_ARGSBODY));
fn->pn_body->append(pn);
/*
* Make sure to deoptimize lexical dependencies that are polluted
* by eval and function statements (which both flag the function as
* having an extensible scope).
*/
if (funbox->hasExtensibleScope() && pc->lexdeps->count()) {
for (AtomDefnRange r = pc->lexdeps->all(); !r.empty(); r.popFront()) {
Definition* dn = r.front().value().get<FullParseHandler>();
MOZ_ASSERT(dn->isPlaceholder());
handler.deoptimizeUsesWithin(dn, fn->pn_pos);
}
}
Rooted<Bindings> bindings(context, funbox->bindings);
if (!funpc.generateBindings(context, tokenStream, alloc, &bindings))
return null();
funbox->bindings = bindings;
return fn;
}
template <>
bool
Parser<FullParseHandler>::checkFunctionArguments()
{
/*
* Non-top-level functions use JSOP_DEFFUN which is a dynamic scope
* operation which means it aliases any bindings with the same name.
*/
if (FuncStmtSet* set = pc->funcStmts) {
for (FuncStmtSet::Range r = set->all(); !r.empty(); r.popFront()) {
PropertyName* name = r.front()->asPropertyName();
if (Definition* dn = pc->decls().lookupFirst(name))
dn->pn_dflags |= PND_CLOSED;
}
}
/* Time to implement the odd semantics of 'arguments'. */
HandlePropertyName arguments = context->names().arguments;
/*
* As explained by the ContextFlags::funArgumentsHasLocalBinding comment,
* create a declaration for 'arguments' if there are any unbound uses in
* the function body.
*/
for (AtomDefnRange r = pc->lexdeps->all(); !r.empty(); r.popFront()) {
if (r.front().key() == arguments) {
Definition* dn = r.front().value().get<FullParseHandler>();
pc->lexdeps->remove(arguments);
dn->pn_dflags |= PND_IMPLICITARGUMENTS;
if (!pc->define(tokenStream, arguments, dn, Definition::VAR))
return false;
pc->sc->asFunctionBox()->usesArguments = true;
break;
}
}
Definition* maybeArgDef = pc->decls().lookupFirst(arguments);
bool argumentsHasBinding = !!maybeArgDef;
// ES6 9.2.13.17 says that a lexical binding of 'arguments' shadows the
// arguments object.
bool argumentsHasLocalBinding = maybeArgDef && (maybeArgDef->kind() != Definition::ARG &&
maybeArgDef->kind() != Definition::LET &&
maybeArgDef->kind() != Definition::CONSTANT);
/*
* Even if 'arguments' isn't explicitly mentioned, dynamic name lookup
* forces an 'arguments' binding.
*/
if (!argumentsHasBinding && pc->sc->bindingsAccessedDynamically()) {
ParseNode* pn = newName(arguments);
if (!pn)
return false;
if (!pc->define(tokenStream, arguments, pn, Definition::VAR))
return false;
argumentsHasBinding = true;
argumentsHasLocalBinding = true;
}
/*
* Now that all possible 'arguments' bindings have been added, note whether
* 'arguments' has a local binding and whether it unconditionally needs an
* arguments object. (Also see the flags' comments in ContextFlags.)
*/
if (argumentsHasLocalBinding) {
FunctionBox* funbox = pc->sc->asFunctionBox();
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 must be created
* eagerly. The debugger can walk the scope chain and observe any
* values along it.
*/
if (pc->sc->hasDebuggerStatement())
funbox->setDefinitelyNeedsArgsObj();
/*
* Check whether any parameters have been assigned within this
* function. If the arguments object is unmapped (strict mode or
* function with default/rest/destructing args), parameters do not alias
* arguments[i], and to make the arguments object reflect initial
* parameter values prior to any mutation we create it eagerly whenever
* parameters are (or might, in the case of calls to eval) assigned.
*/
if (!funbox->hasMappedArgsObj()) {
for (AtomDefnListMap::Range r = pc->decls().all(); !r.empty(); r.popFront()) {
DefinitionList& dlist = r.front().value();
for (DefinitionList::Range dr = dlist.all(); !dr.empty(); dr.popFront()) {
Definition* dn = dr.front<FullParseHandler>();
if (dn->kind() == Definition::ARG && dn->isAssigned())
funbox->setDefinitelyNeedsArgsObj();
}
}
}
}
return true;
}
template <>
bool
Parser<SyntaxParseHandler>::checkFunctionArguments()
{
if (pc->lexdeps->lookup(context->names().arguments))
pc->sc->asFunctionBox()->usesArguments = true;
return true;
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::functionBody(InHandling inHandling, YieldHandling yieldHandling,
FunctionSyntaxKind kind, FunctionBodyType type)
{
MOZ_ASSERT(pc->sc->isFunctionBox());
MOZ_ASSERT(!pc->funHasReturnExpr && !pc->funHasReturnVoid);
#ifdef DEBUG
uint32_t startYieldOffset = pc->lastYieldOffset;
#endif
Node pn;
if (type == StatementListBody) {
pn = statements(yieldHandling);
if (!pn)
return null();
} else {
MOZ_ASSERT(type == ExpressionBody);
Node kid = assignExpr(inHandling, yieldHandling, TripledotProhibited);
if (!kid)
return null();
pn = handler.newReturnStatement(kid, handler.getPosition(kid));
if (!pn)
return null();
}
switch (pc->generatorKind()) {
case NotGenerator:
MOZ_ASSERT(pc->lastYieldOffset == startYieldOffset);
break;
case LegacyGenerator:
// FIXME: Catch these errors eagerly, in Parser::yieldExpression.
MOZ_ASSERT(pc->lastYieldOffset != startYieldOffset);
if (kind == Arrow) {
reportWithOffset(ParseError, false, pc->lastYieldOffset,
JSMSG_YIELD_IN_ARROW, js_yield_str);
return null();
}
if (type == ExpressionBody) {
reportBadReturn(pn, ParseError,
JSMSG_BAD_GENERATOR_RETURN,
JSMSG_BAD_ANON_GENERATOR_RETURN);
return null();
}
break;
case StarGenerator:
MOZ_ASSERT(kind != Arrow);
MOZ_ASSERT(type == StatementListBody);
break;
}
if (pc->isGenerator()) {
MOZ_ASSERT(type == StatementListBody);
Node generator = newName(context->names().dotGenerator);
if (!generator)
return null();
if (!pc->define(tokenStream, context->names().dotGenerator, generator, Definition::VAR))
return null();
generator = newName(context->names().dotGenerator);
if (!generator)
return null();
if (!noteNameUse(context->names().dotGenerator, generator))
return null();
if (!handler.prependInitialYield(pn, generator))
return null();
}
if (kind != Arrow) {
// Define the 'arguments' and 'this' bindings if necessary. Arrow
// functions don't have these bindings.
if (!checkFunctionArguments())
return null();
if (!defineFunctionThis())
return null();
}
return pn;
}
/* See comment for use in Parser::functionDef. */
template <>
bool
Parser<FullParseHandler>::makeDefIntoUse(Definition* dn, ParseNode* pn, HandleAtom atom)
{
/* Turn pn into a definition. */
pc->updateDecl(tokenStream, atom, pn);
/* Change all uses of dn to be uses of pn. */
for (ParseNode* pnu = dn->dn_uses; pnu; pnu = pnu->pn_link) {
MOZ_ASSERT(pnu->isUsed());
MOZ_ASSERT(!pnu->isDefn());
pnu->pn_lexdef = (Definition*) pn;
pn->pn_dflags |= pnu->pn_dflags & PND_USE2DEF_FLAGS;
}
pn->pn_dflags |= dn->pn_dflags & PND_USE2DEF_FLAGS;
pn->dn_uses = dn;
/*
* A PNK_FUNCTION node must be a definition, so convert shadowed function
* statements into nops. This is valid since all body-level function
* statement initialization happens at the beginning of the function
* (thus, only the last statement's effect is visible). E.g., in
*
* function outer() {
* function g() { return 1 }
* assertEq(g(), 2);
* function g() { return 2 }
* assertEq(g(), 2);
* }
*
* both asserts are valid.
*/
if (dn->getKind() == PNK_FUNCTION) {
MOZ_ASSERT(dn->functionIsHoisted());
pn->dn_uses = dn->pn_link;
handler.prepareNodeForMutation(dn);
dn->setKind(PNK_NOP);
dn->setArity(PN_NULLARY);
dn->setDefn(false);
return true;
}
/*
* If dn is in [var, const, let] and has an initializer, then we
* must rewrite it to be an assignment node, whose freshly allocated
* left-hand side becomes a use of pn.
*/
if (dn->canHaveInitializer()) {
if (ParseNode* rhs = dn->expr()) {
ParseNode* lhs = handler.makeAssignment(dn, rhs);
if (!lhs)
return false;
pn->dn_uses = lhs;
dn->pn_link = nullptr;
dn = (Definition*) lhs;
}
}
/* Turn dn into a use of pn. */
MOZ_ASSERT(dn->isKind(PNK_NAME));
MOZ_ASSERT(dn->isArity(PN_NAME));
MOZ_ASSERT(dn->pn_atom == atom);
dn->setOp((CodeSpec[dn->getOp()].format & JOF_SET) ? JSOP_SETNAME : JSOP_GETNAME);
dn->setDefn(false);
dn->setUsed(true);
dn->pn_lexdef = (Definition*) pn;
dn->pn_scopecoord.makeFree();
dn->pn_dflags &= ~PND_BOUND;
return true;
}
/*
* Helper class for creating bindings.
*
* The same instance can be used more than once by repeatedly calling
* setNameNode() followed by bind().
*
* In the destructuring case, bind() is called indirectly from the variable
* declaration parser by way of checkDestructuringPattern and its friends.
*/
template <typename ParseHandler>
struct BindData
{
struct LetData {
explicit LetData(ExclusiveContext* cx) : blockObj(cx) {}
VarContext varContext;
RootedStaticBlockObject blockObj;
unsigned overflow;
};
explicit BindData(ExclusiveContext* cx)
: kind_(Uninitialized), nameNode_(ParseHandler::null()), letData_(cx)
{}
void initLexical(VarContext varContext, JSOp op, StaticBlockObject* blockObj,
unsigned overflow)
{
init(LexicalBinding, op, op == JSOP_DEFCONST);
letData_.varContext = varContext;
letData_.blockObj = blockObj;
letData_.overflow = overflow;
}
void initVar(JSOp op) {
init(VarBinding, op, false);
}
void initDestructuring(JSOp op) {
init(DestructuringBinding, op, false);
}
void setNameNode(typename ParseHandler::Node pn) {
MOZ_ASSERT(isInitialized());
nameNode_ = pn;
}
typename ParseHandler::Node nameNode() {
MOZ_ASSERT(isInitialized());
return nameNode_;
}
JSOp op() {
MOZ_ASSERT(isInitialized());
return op_;
}
bool isConst() {
MOZ_ASSERT(isInitialized());
return isConst_;
}
const LetData& letData() {
MOZ_ASSERT(kind_ == LexicalBinding);
return letData_;
}
bool bind(HandlePropertyName name, Parser<ParseHandler>* parser) {
MOZ_ASSERT(isInitialized());
MOZ_ASSERT(nameNode_ != ParseHandler::null());
switch (kind_) {
case LexicalBinding:
return Parser<ParseHandler>::bindLexical(this, name, parser);
case VarBinding:
return Parser<ParseHandler>::bindVar(this, name, parser);
case DestructuringBinding:
return Parser<ParseHandler>::bindDestructuringArg(this, name, parser);
default:
MOZ_CRASH();
}
nameNode_ = ParseHandler::null();
}
private:
enum BindingKind {
Uninitialized,
LexicalBinding,
VarBinding,
DestructuringBinding
};
BindingKind kind_;
// Name node for definition processing and error source coordinates.
typename ParseHandler::Node nameNode_;
JSOp op_; // Prologue bytecode or nop.
bool isConst_; // Whether this is a const binding.
LetData letData_;
bool isInitialized() {
return kind_ != Uninitialized;
}
void init(BindingKind kind, JSOp op, bool isConst) {
MOZ_ASSERT(!isInitialized());
kind_ = kind;
op_ = op;
isConst_ = isConst;
}
};
template <typename ParseHandler>
JSFunction*
Parser<ParseHandler>::newFunction(HandleAtom atom, FunctionSyntaxKind kind,
GeneratorKind generatorKind, HandleObject proto)
{
MOZ_ASSERT_IF(kind == Statement, atom != nullptr);
RootedFunction fun(context);
gc::AllocKind allocKind = gc::AllocKind::FUNCTION;
JSFunction::Flags flags;
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:
flags = (generatorKind == NotGenerator
? JSFunction::INTERPRETED_NORMAL
: JSFunction::INTERPRETED_GENERATOR);
}
fun = NewFunctionWithProto(context, nullptr, 0, flags, nullptr, atom, proto,
allocKind, TenuredObject);
if (!fun)
return nullptr;
if (options().selfHostingMode)
fun->setIsSelfHostedBuiltin();
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);
}
/*
* The function LexicalLookup searches a static binding for the given name in
* the stack of statements enclosing the statement currently being parsed. Each
* statement that introduces a new scope has a corresponding scope object, on
* which the bindings for that scope are stored. LexicalLookup either returns
* the innermost statement which has a scope object containing a binding with
* the given name, or nullptr.
*/
template <class ContextT>
static StmtInfoPC*
LexicalLookup(ContextT* ct, HandleAtom atom, StmtInfoPC* stmt = nullptr)
{
RootedId id(ct->sc->context, AtomToId(atom));
if (!stmt)
stmt = ct->innermostScopeStmt();
for (; stmt; stmt = stmt->enclosingScope) {
/*
* With-statements introduce dynamic bindings. Since dynamic bindings
* can potentially override any static bindings introduced by statements
* further up the stack, we have to abort the search.
*/
if (stmt->type == StmtType::WITH && !ct->sc->isDotVariable(atom))
break;
// Skip statements that do not introduce a new scope
if (!stmt->isBlockScope)
continue;
StaticBlockObject& blockObj = stmt->staticBlock();
Shape* shape = blockObj.lookup(ct->sc->context, id);
if (shape)
return stmt;
}
return stmt;
}
template <typename ParseHandler>
typename ParseHandler::DefinitionNode
Parser<ParseHandler>::getOrCreateLexicalDependency(ParseContext<ParseHandler>* pc, JSAtom* atom)
{
AtomDefnAddPtr p = pc->lexdeps->lookupForAdd(atom);
if (p)
return p.value().get<ParseHandler>();
DefinitionNode dn = handler.newPlaceholder(atom, pc->blockid(), pos());
if (!dn)
return ParseHandler::nullDefinition();
DefinitionSingle def = DefinitionSingle::new_<ParseHandler>(dn);
if (!pc->lexdeps->add(p, atom, def))
return ParseHandler::nullDefinition();
return dn;
}
static bool
ConvertDefinitionToNamedLambdaUse(TokenStream& ts, ParseContext<FullParseHandler>* pc,
FunctionBox* funbox, Definition* dn)
{
dn->setOp(JSOP_CALLEE);
if (!dn->pn_scopecoord.setSlot(ts, 0))
return false;
dn->pn_blockid = pc->blockid();
dn->pn_dflags |= PND_BOUND;
MOZ_ASSERT(dn->kind() == Definition::NAMED_LAMBDA);
/*
* Since 'dn' is a placeholder, it has not been defined in the
* ParseContext and hence we must manually flag a closed-over
* callee name as needing a dynamic scope (this is done for all
* definitions in the ParseContext by generateBindings).
*
* If 'dn' has been assigned to, then we also flag the function
* scope has needing a dynamic scope so that dynamic scope
* setter can either ignore the set (in non-strict mode) or
* produce an error (in strict mode).
*/
if (dn->isClosed() || dn->isAssigned())
funbox->setNeedsDeclEnvObject();
return true;
}
static bool
IsNonDominatingInScopedSwitch(ParseContext<FullParseHandler>* pc, HandleAtom name,
Definition* dn)
{
MOZ_ASSERT(dn->isLexical());
StmtInfoPC* stmt = LexicalLookup(pc, name);
if (stmt && stmt->type == StmtType::SWITCH)
return dn->pn_scopecoord.slot() < stmt->firstDominatingLexicalInCase;
return false;
}
static void
AssociateUsesWithOuterDefinition(ParseNode* pnu, Definition* dn, Definition* outer_dn,
bool markUsesAsLexical)
{
uint32_t dflags = markUsesAsLexical ? PND_LEXICAL : 0;
while (true) {
pnu->pn_lexdef = outer_dn;
pnu->pn_dflags |= dflags;
if (!pnu->pn_link)
break;
pnu = pnu->pn_link;
}
pnu->pn_link = outer_dn->dn_uses;
outer_dn->dn_uses = dn->dn_uses;
dn->dn_uses = nullptr;
}
/*
* Beware: this function is called for functions nested in other functions or
* global scripts but not for functions compiled through the Function
* constructor or JSAPI. To always execute code when a function has finished
* parsing, use Parser::functionBody.
*/
template <>
bool
Parser<FullParseHandler>::leaveFunction(ParseNode* fn, ParseContext<FullParseHandler>* outerpc,
FunctionSyntaxKind kind)
{
bool bodyLevel = outerpc->atBodyLevel();
FunctionBox* funbox = fn->pn_funbox;
MOZ_ASSERT(funbox == pc->sc->asFunctionBox());
/* Propagate unresolved lexical names up to outerpc->lexdeps. */
if (pc->lexdeps->count()) {
for (AtomDefnRange r = pc->lexdeps->all(); !r.empty(); r.popFront()) {
JSAtom* atom = r.front().key();
Definition* dn = r.front().value().get<FullParseHandler>();
MOZ_ASSERT(dn->isPlaceholder());
if (atom == funbox->function()->name() && kind == Expression) {
if (!ConvertDefinitionToNamedLambdaUse(tokenStream, pc, funbox, dn))
return false;
continue;
}
Definition* outer_dn = outerpc->decls().lookupFirst(atom);
/*
* Make sure to deoptimize lexical dependencies that are polluted
* by eval and function statements (which both flag the function as
* having an extensible scope) or any enclosing 'with'.
*/
if (funbox->hasExtensibleScope() || funbox->inWith())
handler.deoptimizeUsesWithin(dn, fn->pn_pos);
if (!outer_dn) {
/*
* Create a new placeholder for our outer lexdep. We could
* simply re-use the inner placeholder, but that introduces
* subtleties in the case where we find a later definition
* that captures an existing lexdep. For example:
*
* function f() { function g() { x; } let x; }
*
* Here, g's TOK_UPVARS node lists the placeholder for x,
* which must be captured by the 'let' declaration later,
* since 'let's are hoisted. Taking g's placeholder as our
* own would work fine. But consider:
*
* function f() { x; { function g() { x; } let x; } }
*
* Here, the 'let' must not capture all the uses of f's
* lexdep entry for x, but it must capture the x node
* referred to from g's TOK_UPVARS node. Always turning
* inherited lexdeps into uses of a new outer definition
* allows us to handle both these cases in a natural way.
*/
outer_dn = getOrCreateLexicalDependency(outerpc, atom);
if (!outer_dn)
return false;
}
/*
* Insert dn's uses list at the front of outer_dn's list.
*
* Without loss of generality or correctness, we allow a dn to
* be in inner and outer lexdeps, since the purpose of lexdeps
* is one-pass coordination of name use and definition across
* functions, and if different dn's are used we'll merge lists
* when leaving the inner function.
*
* The dn == outer_dn case arises with generator expressions
* (see LegacyCompExprTransplanter::transplant, the PN_CODE/PN_NAME
* case), and nowhere else, currently.
*/
if (dn != outer_dn) {
if (ParseNode* pnu = dn->dn_uses) {
// In ES6, lexical bindings cannot be accessed until
// initialized. If we are parsing a body-level function,
// it is hoisted to the top, so we conservatively mark all
// uses linked to an outer lexical binding as needing TDZ
// checks. e.g.,
//
// function outer() {
// inner2();
// function inner() { use(x); }
// function inner2() { inner(); }
// let x;
// }
//
// The use of 'x' inside 'inner' needs to be marked.
//
// Note that to not be fully conservative requires a call
// graph analysis of all body-level functions to compute
// the transitive closure of which hoisted body level use
// of which function forces TDZ checks on which uses. This
// is unreasonably difficult to do in a single pass parser
// like ours.
//
// Similarly, if we are closing over a lexical binding
// from another case in a switch, those uses also need to
// be marked as needing dead zone checks.
RootedAtom name(context, atom);
bool markUsesAsLexical = outer_dn->isLexical() &&
(bodyLevel ||
IsNonDominatingInScopedSwitch(outerpc, name, outer_dn));
AssociateUsesWithOuterDefinition(pnu, dn, outer_dn, markUsesAsLexical);
}
outer_dn->pn_dflags |= dn->pn_dflags & ~PND_PLACEHOLDER;
}
/* Mark the outer dn as escaping. */
outer_dn->pn_dflags |= PND_CLOSED;
}
}
Rooted<Bindings> bindings(context, funbox->bindings);
if (!pc->generateBindings(context, tokenStream, alloc, &bindings))
return false;
funbox->bindings = bindings;
return true;
}
template <>
bool
Parser<SyntaxParseHandler>::leaveFunction(Node fn, ParseContext<SyntaxParseHandler>* outerpc,
FunctionSyntaxKind kind)
{
FunctionBox* funbox = pc->sc->asFunctionBox();
return addFreeVariablesFromLazyFunction(funbox->function(), outerpc);
}
/*
* defineArg is called for both the arguments of a regular function definition
* and the arguments specified by the Function constructor.
*
* The 'disallowDuplicateArgs' 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.)
*
* If 'duplicatedArg' is non-null, then DefineArg assigns to it any previous
* argument with the same name. The caller may use this to report an error when
* one of the abovementioned features occurs after a duplicate.
*/
template <typename ParseHandler>
bool
Parser<ParseHandler>::defineArg(Node funcpn, HandlePropertyName name,
bool disallowDuplicateArgs, Node* duplicatedArg)
{
SharedContext* sc = pc->sc;
/* Handle duplicate argument names. */
if (DefinitionNode prevDecl = pc->decls().lookupFirst(name)) {
Node pn = handler.getDefinitionNode(prevDecl);
/*
* 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 (sc->needStrictChecks()) {
JSAutoByteString bytes;
if (!AtomToPrintableString(context, name, &bytes))
return false;
if (!report(ParseStrictError, pc->sc->strict(), pn,
JSMSG_DUPLICATE_FORMAL, bytes.ptr()))
{
return false;
}
}
if (disallowDuplicateArgs) {
report(ParseError, false, pn, JSMSG_BAD_DUP_ARGS);
return false;
}
if (duplicatedArg)
*duplicatedArg = pn;
/* ParseContext::define assumes and asserts prevDecl is not in decls. */
MOZ_ASSERT(handler.getDefinitionKind(prevDecl) == Definition::ARG);
pc->prepareToAddDuplicateArg(name, prevDecl);
}
Node argpn = newName(name);
if (!argpn)
return false;
if (!checkStrictBinding(name, argpn))
return false;
handler.addFunctionArgument(funcpn, argpn);
return pc->define(tokenStream, name, argpn, Definition::ARG);
}
template <typename ParseHandler>
/* static */ bool
Parser<ParseHandler>::bindDestructuringArg(BindData<ParseHandler>* data,
HandlePropertyName name, Parser<ParseHandler>* parser)
{
ParseContext<ParseHandler>* pc = parser->pc;
MOZ_ASSERT(pc->sc->isFunctionBox());
if (pc->decls().lookupFirst(name)) {
parser->report(ParseError, false, null(), JSMSG_BAD_DUP_ARGS);
return false;
}
if (!parser->checkStrictBinding(name, data->nameNode()))
return false;
return pc->define(parser->tokenStream, name, data->nameNode(), Definition::VAR);
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::functionArguments(YieldHandling yieldHandling, FunctionSyntaxKind kind,
Node funcpn, bool* hasRest)
{
FunctionBox* funbox = pc->sc->asFunctionBox();
*hasRest = false;
bool parenFreeArrow = false;
TokenStream::Modifier modifier = TokenStream::None;
if (kind == Arrow) {
TokenKind tt;
if (!tokenStream.peekToken(&tt, TokenStream::Operand))
return false;
if (tt == TOK_NAME)
parenFreeArrow = true;
else
modifier = TokenStream::Operand;
}
if (!parenFreeArrow) {
TokenKind tt;
if (!tokenStream.getToken(&tt, modifier))
return false;
if (tt != TOK_LP) {
report(ParseError, false, null(),
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_ARGSBODY);
if (!argsbody)
return false;
handler.setFunctionBody(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 hasDefaults = false;
Node duplicatedArg = null();
bool disallowDuplicateArgs = kind == Arrow || kind == Method || kind == ClassConstructor;
if (IsGetterKind(kind)) {
report(ParseError, false, null(), JSMSG_ACCESSOR_WRONG_ARGS, "getter", "no", "s");
return false;
}
while (true) {
if (*hasRest) {
report(ParseError, false, null(), JSMSG_PARAMETER_AFTER_REST);
return false;
}
TokenKind tt;
if (!tokenStream.getToken(&tt, TokenStream::Operand))
return false;
MOZ_ASSERT_IF(parenFreeArrow, tt == TOK_NAME);
switch (tt) {
case TOK_LB:
case TOK_LC:
{
/* See comment below in the TOK_NAME case. */
disallowDuplicateArgs = true;
if (duplicatedArg) {
report(ParseError, false, duplicatedArg, JSMSG_BAD_DUP_ARGS);
return false;
}
funbox->hasDestructuringArgs = true;
/*
* A destructuring formal parameter turns into one or more
* local variables initialized from properties of a single
* anonymous positional parameter, so here we must tweak our
* binder and its data.
*/
BindData<ParseHandler> data(context);
data.initDestructuring(JSOP_DEFVAR);
Node destruct = destructuringExprWithoutYield(yieldHandling, &data, tt,
JSMSG_YIELD_IN_DEFAULT);
if (!destruct)
return false;
/*
* Make a single anonymous positional parameter, and store
* destructuring expression into the node.
*/
HandlePropertyName name = context->names().empty;
Node arg = newName(name);
if (!arg)
return false;
handler.addFunctionArgument(funcpn, arg);
if (!pc->define(tokenStream, name, arg, Definition::ARG))
return false;
handler.setLastFunctionArgumentDestructuring(funcpn, destruct);
break;
}
case TOK_YIELD:
if (!checkYieldNameValidity())
return false;
MOZ_ASSERT(yieldHandling == YieldIsName);
goto TOK_NAME;
case TOK_TRIPLEDOT:
{
if (IsSetterKind(kind)) {
report(ParseError, false, null(),
JSMSG_ACCESSOR_WRONG_ARGS, "setter", "one", "");
return false;
}
*hasRest = true;
if (!tokenStream.getToken(&tt))
return false;
// FIXME: This fails to handle a rest parameter named |yield|
// correctly outside of generators: that is,
// |var f = (...yield) => 42;| should be valid code!
// When this is fixed, make sure to consult both
// |yieldHandling| and |checkYieldNameValidity| for
// correctness until legacy generator syntax is removed.
if (tt != TOK_NAME) {
report(ParseError, false, null(), JSMSG_NO_REST_NAME);
return false;
}
disallowDuplicateArgs = true;
if (duplicatedArg) {
// Has duplicated args before the rest parameter.
report(ParseError, false, duplicatedArg, JSMSG_BAD_DUP_ARGS);
return false;
}
goto TOK_NAME;
}
TOK_NAME:
case TOK_NAME:
{
if (parenFreeArrow)
funbox->setStart(tokenStream);
RootedPropertyName name(context, tokenStream.currentName());
if (!defineArg(funcpn, name, disallowDuplicateArgs, &duplicatedArg))
return false;
break;
}
default:
report(ParseError, false, null(), JSMSG_MISSING_FORMAL);
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) {
report(ParseError, false, null(), JSMSG_REST_WITH_DEFAULT);
return false;
}
disallowDuplicateArgs = true;
if (duplicatedArg) {
report(ParseError, false, duplicatedArg, JSMSG_BAD_DUP_ARGS);
return false;
}
if (!hasDefaults) {
hasDefaults = true;
// The Function.length property is the number of formals
// before the first default argument.
funbox->length = pc->numArgs() - 1;
}
Node def_expr = assignExprWithoutYield(yieldHandling, JSMSG_YIELD_IN_DEFAULT);
if (!def_expr)
return false;
if (!handler.setLastFunctionArgumentDefault(funcpn, def_expr))
return false;
}
if (parenFreeArrow || IsSetterKind(kind))
break;
if (!tokenStream.matchToken(&matched, TOK_COMMA))
return false;
if (!matched)
break;
}
if (!parenFreeArrow) {
TokenKind tt;
if (!tokenStream.getToken(&tt))
return false;
if (tt != TOK_RP) {
if (IsSetterKind(kind)) {
report(ParseError, false, null(),
JSMSG_ACCESSOR_WRONG_ARGS, "setter", "one", "");
return false;
}
report(ParseError, false, null(), JSMSG_PAREN_AFTER_FORMAL);
return false;
}
}
if (!hasDefaults)
funbox->length = pc->numArgs() - *hasRest;
} else if (IsSetterKind(kind)) {
report(ParseError, false, null(), JSMSG_ACCESSOR_WRONG_ARGS, "setter", "one", "");
return false;
}
return true;
}
template <>
bool
Parser<FullParseHandler>::checkFunctionDefinition(HandlePropertyName funName,
ParseNode** pn_, FunctionSyntaxKind kind,
bool* pbodyProcessed)
{
ParseNode*& pn = *pn_;
*pbodyProcessed = false;
/* Function statements add a binding to the enclosing scope. */
bool bodyLevel = pc->atBodyLevel();
if (kind == Statement) {
/*
* Handle redeclaration and optimize cases where we can statically bind the
* function (thereby avoiding JSOP_DEFFUN and dynamic name lookup).
*/
if (Definition* dn = pc->decls().lookupFirst(funName)) {
MOZ_ASSERT(!dn->isUsed());
MOZ_ASSERT(dn->isDefn());
bool throwRedeclarationError = dn->kind() == Definition::CONSTANT ||
dn->kind() == Definition::LET;
if (options().extraWarningsOption || throwRedeclarationError) {
JSAutoByteString name;
ParseReportKind reporter = throwRedeclarationError
? ParseError
: ParseExtraWarning;
if (!AtomToPrintableString(context, funName, &name) ||
!report(reporter, false, nullptr, JSMSG_REDECLARED_VAR,
Definition::kindString(dn->kind()), name.ptr()))
{
return false;
}
}
/*
* Body-level function statements are effectively variable
* declarations where the initialization is hoisted to the
* beginning of the block. This means that any other variable
* declaration with the same name is really just an assignment to
* the function's binding (which is mutable), so turn any existing
* declaration into a use.
*/
if (bodyLevel) {
if (dn->kind() == Definition::ARG) {
// The exception to the above comment is when the function
// has the same name as an argument. Then the argument node
// remains a definition. But change the function node pn so
// that it knows where the argument is located.
pn->setOp(JSOP_GETARG);
pn->setDefn(true);
pn->pn_scopecoord = dn->pn_scopecoord;
pn->pn_blockid = dn->pn_blockid;
pn->pn_dflags |= PND_BOUND;
dn->markAsAssigned();
} else {
if (!makeDefIntoUse(dn, pn, funName))
return false;
}
}
} else if (bodyLevel) {
/*
* If this function was used before it was defined, claim the
* pre-created definition node for this function that primaryExpr
* put in pc->lexdeps on first forward reference, and recycle pn.
*/
if (Definition* fn = pc->lexdeps.lookupDefn<FullParseHandler>(funName)) {
MOZ_ASSERT(fn->isDefn());
fn->setKind(PNK_FUNCTION);
fn->setArity(PN_CODE);
fn->pn_pos.begin = pn->pn_pos.begin;
fn->pn_pos.end = pn->pn_pos.end;
fn->pn_body = nullptr;
fn->pn_scopecoord.makeFree();
pc->lexdeps->remove(funName);
handler.freeTree(pn);
pn = fn;
}
if (!pc->define(tokenStream, funName, pn, Definition::VAR))
return false;
}
if (bodyLevel) {
MOZ_ASSERT(pn->functionIsHoisted());
MOZ_ASSERT(pc->sc->isGlobalContext() == pn->pn_scopecoord.isFree());
} else {
/*
* As a SpiderMonkey-specific extension, non-body-level function
* statements (e.g., functions in an "if" or "while" block) are
* dynamically bound when control flow reaches the statement.
*/
MOZ_ASSERT(!pc->sc->strict());
MOZ_ASSERT(pn->pn_scopecoord.isFree());
if (pc->sc->isFunctionBox()) {
FunctionBox* funbox = pc->sc->asFunctionBox();
funbox->setMightAliasLocals();
funbox->setHasExtensibleScope();
}
pn->setOp(JSOP_DEFFUN);
/*
* Instead of setting bindingsAccessedDynamically, which would be
* overly conservative, remember the names of all function
* statements and mark any bindings with the same as aliased at the
* end of functionBody.
*/
if (!pc->funcStmts) {
pc->funcStmts = alloc.new_<FuncStmtSet>(alloc);
if (!pc->funcStmts || !pc->funcStmts->init()) {
ReportOutOfMemory(context);
return false;
}
}
if (!pc->funcStmts->put(funName))
return false;
/*
* Due to the implicit declaration mechanism, 'arguments' will not
* have decls and, even if it did, they will not be noted as closed
* in the emitter. Thus, in the corner case of function statements
* overridding arguments, flag the whole scope as dynamic.
*/
if (funName == context->names().arguments)
pc->sc->setBindingsAccessedDynamically();
}
/* No further binding (in BindNameToSlot) is needed for functions. */
pn->pn_dflags |= PND_BOUND;
} else {
/* A function expression does not introduce any binding. */
pn->setOp(kind == Arrow ? JSOP_LAMBDA_ARROW : JSOP_LAMBDA);
}
// 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.
Rooted<LazyScript*> lazyOuter(context, handler.lazyOuterFunction());
if (lazyOuter) {
RootedFunction fun(context, handler.nextLazyInnerFunction());
MOZ_ASSERT(!fun->isLegacyGenerator());
FunctionBox* funbox = newFunctionBox(pn, fun, pc, Directives(/* strict = */ false),
fun->generatorKind());
if (!funbox)
return false;
if (fun->lazyScript()->needsHomeObject())
funbox->setNeedsHomeObject();
if (!addFreeVariablesFromLazyFunction(fun, pc))
return false;
// 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.
uint32_t userbufBase = lazyOuter->begin() - lazyOuter->column();
if (!tokenStream.advance(fun->lazyScript()->end() - userbufBase))
return false;
*pbodyProcessed = true;
return true;
}
return true;
}
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();
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::addFreeVariablesFromLazyFunction(JSFunction* fun,
ParseContext<ParseHandler>* pc)
{
// Update any definition nodes in this context according to free variables
// in a lazily parsed inner function.
bool bodyLevel = pc->atBodyLevel();
LazyScript* lazy = fun->lazyScript();
LazyScript::FreeVariable* freeVariables = lazy->freeVariables();
for (size_t i = 0; i < lazy->numFreeVariables(); i++) {
JSAtom* atom = freeVariables[i].atom();
// 'arguments' will be implicitly bound within the inner function,
// except if the inner function is an arrow function.
if (atom == context->names().arguments && !fun->isArrow())
continue;
DefinitionNode dn = pc->decls().lookupFirst(atom);
if (!dn) {
dn = getOrCreateLexicalDependency(pc, atom);
if (!dn)
return false;
}
// In ES6, lexical bindings are unaccessible before initialization. If
// the inner function closes over a placeholder definition, we need to
// mark the variable as maybe needing a dead zone check when we emit
// bytecode.
//
// Note that body-level function declaration statements are always
// hoisted to the top, so all accesses to free let variables need the
// dead zone check.
//
// Subtlety: we don't need to check for closing over a non-dominating
// lexical binding in a switch, as lexical declarations currently
// disable syntax parsing. So a non-dominating but textually preceding
// lexical declaration would have aborted syntax parsing, and a
// textually following declaration would return true for
// handler.isPlaceholderDefinition(dn) below.
if (handler.isPlaceholderDefinition(dn) || bodyLevel)
freeVariables[i].setIsHoistedUse();
/* Mark the outer dn as escaping. */
handler.setFlag(handler.getDefinitionNode(dn), PND_CLOSED);
}
PropagateTransitiveParseFlags(lazy, pc->sc);
return true;
}
template <>
bool
Parser<SyntaxParseHandler>::checkFunctionDefinition(HandlePropertyName funName,
Node* pn, FunctionSyntaxKind kind,
bool* pbodyProcessed)
{
*pbodyProcessed = false;
/* Function statements add a binding to the enclosing scope. */
bool bodyLevel = pc->atBodyLevel();
if (kind == Statement) {
/*
* Handle redeclaration and optimize cases where we can statically bind the
* function (thereby avoiding JSOP_DEFFUN and dynamic name lookup).
*/
if (DefinitionNode dn = pc->decls().lookupFirst(funName)) {
if (dn == Definition::CONSTANT || dn == Definition::LET) {
JSAutoByteString name;
if (!AtomToPrintableString(context, funName, &name) ||
!report(ParseError, false, null(), JSMSG_REDECLARED_VAR,
Definition::kindString(dn), name.ptr()))
{
return false;
}
}
} else if (bodyLevel) {
if (pc->lexdeps.lookupDefn<SyntaxParseHandler>(funName))
pc->lexdeps->remove(funName);
if (!pc->define(tokenStream, funName, *pn, Definition::VAR))
return false;
}
if (!bodyLevel && funName == context->names().arguments)
pc->sc->setBindingsAccessedDynamically();
}
if (kind == Arrow) {
/* Arrow functions cannot yet be parsed lazily. */
return abortIfSyntaxParser();
}
return true;
}
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) {
report(ParseError, false, null(), 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);
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>::functionDef(InHandling inHandling, YieldHandling yieldHandling,
HandlePropertyName funName, FunctionSyntaxKind kind,
GeneratorKind generatorKind, InvokedPrediction invoked)
{
MOZ_ASSERT_IF(kind == Statement, funName);
/* Make a TOK_FUNCTION node. */
Node pn = handler.newFunctionDefinition();
if (!pn)
return null();
if (invoked)
pn = handler.setLikelyIIFE(pn);
bool bodyProcessed;
if (!checkFunctionDefinition(funName, &pn, kind, &bodyProcessed))
return null();
if (bodyProcessed)
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, 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);
while (true) {
if (functionArgsAndBody(inHandling, pn, fun, kind, generatorKind, directives,
&newDirectives))
{
break;
}
if (tokenStream.hadError() || directives == newDirectives)
return null();
// Assignment must be monotonic to prevent reparsing iloops
MOZ_ASSERT_IF(directives.strict(), newDirectives.strict());
MOZ_ASSERT_IF(directives.asmJS(), newDirectives.asmJS());
directives = newDirectives;
tokenStream.seek(start);
// functionArgsAndBody may have already set pn->pn_body before failing.
handler.setFunctionBody(pn, null());
}
return pn;
}
template <>
bool
Parser<FullParseHandler>::finishFunctionDefinition(ParseNode* pn, FunctionBox* funbox,
ParseNode* body)
{
pn->pn_pos.end = pos().end;
MOZ_ASSERT(pn->pn_funbox == funbox);
MOZ_ASSERT(pn->pn_body->isKind(PNK_ARGSBODY));
pn->pn_body->append(body);
return true;
}
template <>
bool
Parser<SyntaxParseHandler>::finishFunctionDefinition(Node pn, FunctionBox* funbox,
Node body)
{
// The LazyScript for a lazily parsed function needs to be constructed
// while its ParseContext and associated lexdeps and inner functions are
// still available.
if (funbox->inWith())
return abortIfSyntaxParser();
size_t numFreeVariables = pc->lexdeps->count();
size_t numInnerFunctions = pc->innerFunctions.length();
RootedFunction fun(context, funbox->function());
LazyScript* lazy = LazyScript::CreateRaw(context, fun, numFreeVariables, numInnerFunctions,
versionNumber(), funbox->bufStart, funbox->bufEnd,
funbox->startLine, funbox->startColumn);
if (!lazy)
return false;
LazyScript::FreeVariable* freeVariables = lazy->freeVariables();
size_t i = 0;
for (AtomDefnRange r = pc->lexdeps->all(); !r.empty(); r.popFront())
freeVariables[i++] = LazyScript::FreeVariable(r.front().key());
MOZ_ASSERT(i == numFreeVariables);
HeapPtrFunction* innerFunctions = lazy->innerFunctions();
for (size_t i = 0; i < numInnerFunctions; i++)
innerFunctions[i].init(pc->innerFunctions[i]);
if (pc->sc->strict())
lazy->setStrict();
lazy->setGeneratorKind(funbox->generatorKind());
if (funbox->isLikelyConstructorWrapper())
lazy->setLikelyConstructorWrapper();
if (funbox->isDerivedClassConstructor())
lazy->setIsDerivedClassConstructor();
if (funbox->needsHomeObject())
lazy->setNeedsHomeObject();
PropagateTransitiveParseFlags(funbox, lazy);
fun->initLazyScript(lazy);
return true;
}
template <>
bool
Parser<FullParseHandler>::functionArgsAndBody(InHandling inHandling, ParseNode* pn,
HandleFunction fun, FunctionSyntaxKind kind,
GeneratorKind generatorKind,
Directives inheritedDirectives,
Directives* newDirectives)
{
ParseContext<FullParseHandler>* outerpc = pc;
// Create box for fun->object early to protect against last-ditch GC.
FunctionBox* funbox = newFunctionBox(pn, fun, pc, inheritedDirectives, generatorKind);
if (!funbox)
return false;
if (kind == DerivedClassConstructor)
funbox->setDerivedClassConstructor();
YieldHandling yieldHandling = generatorKind != NotGenerator ? YieldIsKeyword : YieldIsName;
// We need to roll back the block scope vector if syntax parsing fails.
uint32_t oldBlockScopesLength = blockScopes.length();
// 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() && !funbox->isGenerator())
break;
Parser<SyntaxParseHandler>* parser = handler.syntaxParser;
if (!parser)
break;
{
// 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;
ParseContext<SyntaxParseHandler> funpc(parser, outerpc, SyntaxParseHandler::null(),
funbox, newDirectives);
if (!funpc.init(*parser))
return false;
if (!parser->functionArgsAndBodyGeneric(inHandling, yieldHandling,
SyntaxParseHandler::NodeGeneric, fun, kind))
{
if (parser->hadAbortedSyntaxParse()) {
// Try again with a full parse.
parser->clearAbortedSyntaxParse();
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;
}
if (!addFreeVariablesFromLazyFunction(fun, pc))
return false;
pn->pn_blockid = outerpc->blockid();
PropagateTransitiveParseFlags(funbox, outerpc->sc);
return true;
} while (false);
blockScopes.resize(oldBlockScopesLength);
// Continue doing a full parse for this inner function.
ParseContext<FullParseHandler> funpc(this, pc, pn, funbox, newDirectives);
if (!funpc.init(*this))
return false;
if (!functionArgsAndBodyGeneric(inHandling, yieldHandling, pn, fun, kind))
return false;
if (!leaveFunction(pn, outerpc, kind))
return false;
pn->pn_blockid = outerpc->blockid();
/*
* Fruit of the poisonous tree: if a closure contains a dynamic name access
* (eval, with, etc), we consider the parent to do the same. The reason is
* that the deoptimizing effects of dynamic name access apply equally to
* parents: any local can be read at runtime.
*/
PropagateTransitiveParseFlags(funbox, outerpc->sc);
return true;
}
template <>
bool
Parser<SyntaxParseHandler>::functionArgsAndBody(InHandling inHandling, Node pn, HandleFunction fun,
FunctionSyntaxKind kind,
GeneratorKind generatorKind,
Directives inheritedDirectives,
Directives* newDirectives)
{
ParseContext<SyntaxParseHandler>* outerpc = pc;
// Create box for fun->object early to protect against last-ditch GC.
FunctionBox* funbox = newFunctionBox(pn, fun, pc, inheritedDirectives, generatorKind);
if (!funbox)
return false;
// Initialize early for possible flags mutation via destructuringExpr.
ParseContext<SyntaxParseHandler> funpc(this, pc, handler.null(), funbox, newDirectives);
if (!funpc.init(*this))
return false;
YieldHandling yieldHandling = generatorKind != NotGenerator ? YieldIsKeyword : YieldIsName;
if (!functionArgsAndBodyGeneric(inHandling, yieldHandling, pn, fun, kind))
return false;
if (!leaveFunction(pn, outerpc, kind))
return false;
// This is a lazy function inner to another lazy function. Remember 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.
MOZ_ASSERT(fun->lazyScript());
return outerpc->innerFunctions.append(fun);
}
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;
return handler.addToCallSiteObject(callSiteObj, rawNode, cookedNode);
}
template <>
ParseNode*
Parser<FullParseHandler>::standaloneLazyFunction(HandleFunction fun, bool strict,
GeneratorKind generatorKind)
{
MOZ_ASSERT(checkOptionsCalled);
Node pn = handler.newFunctionDefinition();
if (!pn)
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 (!tokenStream.peekTokenPos(&pn->pn_pos))
return null();
RootedObject enclosing(context, fun->lazyScript()->enclosingScope());
Directives directives(/* strict = */ strict);
FunctionBox* funbox = newFunctionBox(pn, fun, directives, generatorKind, enclosing);
if (!funbox)
return null();
funbox->length = fun->nargs() - fun->hasRest();
if (fun->lazyScript()->isDerivedClassConstructor())
funbox->setDerivedClassConstructor();
Directives newDirectives = directives;
ParseContext<FullParseHandler> funpc(this, /* parent = */ nullptr, pn, funbox,
&newDirectives);
if (!funpc.init(*this))
return null();
YieldHandling yieldHandling = generatorKind != NotGenerator ? YieldIsKeyword : YieldIsName;
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;
if (!functionArgsAndBodyGeneric(InAllowed, yieldHandling, pn, fun, syntaxKind)) {
MOZ_ASSERT(directives == newDirectives);
return null();
}
if (fun->isNamedLambda()) {
if (AtomDefnPtr p = pc->lexdeps->lookup(fun->name())) {
Definition* dn = p.value().get<FullParseHandler>();
if (!ConvertDefinitionToNamedLambdaUse(tokenStream, pc, funbox, dn))
return nullptr;
}
}
Rooted<Bindings> bindings(context, funbox->bindings);
if (!pc->generateBindings(context, tokenStream, alloc, &bindings))
return null();
funbox->bindings = bindings;
if (!FoldConstants(context, &pn, this))
return null();
return pn;
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::functionArgsAndBodyGeneric(InHandling inHandling,
YieldHandling yieldHandling, Node pn,
HandleFunction fun, FunctionSyntaxKind kind)
{
// 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.
bool hasRest;
if (!functionArguments(yieldHandling, kind, pn, &hasRest))
return false;
FunctionBox* funbox = pc->sc->asFunctionBox();
fun->setArgCount(pc->numArgs());
if (hasRest)
fun->setHasRest();
if (kind == Arrow) {
bool matched;
if (!tokenStream.matchToken(&matched, TOK_ARROW))
return false;
if (!matched) {
report(ParseError, false, null(), JSMSG_BAD_ARROW_ARGS);
return false;
}
}
// Parse the function body.
FunctionBodyType bodyType = StatementListBody;
TokenKind tt;