| /* -*- 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; |
|