/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80: * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef frontend_ParseContext_h #define frontend_ParseContext_h #include "ds/Nestable.h" #include "frontend/ErrorReporter.h" #include "frontend/NameAnalysisTypes.h" // DeclaredNameInfo, FunctionBoxVector #include "frontend/NameCollections.h" #include "frontend/ParserAtom.h" // TaggedParserAtomIndex #include "frontend/ScriptIndex.h" // ScriptIndex #include "frontend/SharedContext.h" #include "js/friend/ErrorMessages.h" // JSMSG_* #include "vm/GeneratorAndAsyncKind.h" // js::GeneratorKind, js::FunctionAsyncKind #include "vm/WellKnownAtom.h" // js_*_str namespace js { namespace frontend { class ParserBase; class UsedNameTracker; struct CompilationState; const char* DeclarationKindString(DeclarationKind kind); // Returns true if the declaration is `var` or equivalent. bool DeclarationKindIsVar(DeclarationKind kind); bool DeclarationKindIsParameter(DeclarationKind kind); /* * The struct ParseContext stores information about the current parsing context, * which is part of the parser state (see the field Parser::pc). The current * parsing context is either the global context, or the function currently being * parsed. When the parser encounters a function definition, it creates a new * ParseContext, makes it the new current context. */ class ParseContext : public Nestable { public: // The intra-function statement stack. // // Used for early error checking that depend on the nesting structure of // statements, such as continue/break targets, labels, and unbraced // lexical declarations. class Statement : public Nestable { StatementKind kind_; public: using Nestable::enclosing; using Nestable::findNearest; Statement(ParseContext* pc, StatementKind kind) : Nestable(&pc->innermostStatement_), kind_(kind) {} template inline bool is() const; template inline T& as(); StatementKind kind() const { return kind_; } void refineForKind(StatementKind newForKind) { MOZ_ASSERT(kind_ == StatementKind::ForLoop); MOZ_ASSERT(newForKind == StatementKind::ForInLoop || newForKind == StatementKind::ForOfLoop); kind_ = newForKind; } }; class LabelStatement : public Statement { TaggedParserAtomIndex label_; public: LabelStatement(ParseContext* pc, TaggedParserAtomIndex label) : Statement(pc, StatementKind::Label), label_(label) {} TaggedParserAtomIndex label() const { return label_; } }; struct ClassStatement : public Statement { FunctionBox* constructorBox; explicit ClassStatement(ParseContext* pc) : Statement(pc, StatementKind::Class), constructorBox(nullptr) {} }; // The intra-function scope stack. // // Tracks declared and used names within a scope. class Scope : public Nestable { // Names declared in this scope. Corresponds to the union of // VarDeclaredNames and LexicallyDeclaredNames in the ES spec. // // A 'var' declared name is a member of the declared name set of every // scope in its scope contour. // // A lexically declared name is a member only of the declared name set of // the scope in which it is declared. PooledMapPtr declared_; // FunctionBoxes in this scope that need to be considered for Annex // B.3.3 semantics. This is checked on Scope exit, as by then we have // all the declared names and would know if Annex B.3.3 is applicable. PooledVectorPtr possibleAnnexBFunctionBoxes_; // Monotonically increasing id. uint32_t id_; // Scope size info, relevant for scopes in generators and async functions // only. During parsing, this is the estimated number of slots needed for // nested scopes inside this one. When the parser leaves a scope, this is // set to UINT32_MAX if there are too many bindings overrall to store them // in stack frames, and 0 otherwise. uint32_t sizeBits_ = 0; bool maybeReportOOM(ParseContext* pc, bool result) { if (!result) { ReportOutOfMemory(pc->sc()->fc_); } return result; } public: using DeclaredNamePtr = DeclaredNameMap::Ptr; using AddDeclaredNamePtr = DeclaredNameMap::AddPtr; using Nestable::enclosing; explicit inline Scope(ParserBase* parser); explicit inline Scope(FrontendContext* fc, ParseContext* pc, UsedNameTracker& usedNames); void dump(ParseContext* pc, ParserBase* parser); uint32_t id() const { return id_; } [[nodiscard]] bool init(ParseContext* pc) { if (id_ == UINT32_MAX) { pc->errorReporter_.errorNoOffset(JSMSG_NEED_DIET, js_script_str); return false; } return declared_.acquire(pc->sc()->fc_); } bool isEmpty() const { return declared_->all().empty(); } uint32_t declaredCount() const { size_t count = declared_->count(); MOZ_ASSERT(count <= UINT32_MAX); return uint32_t(count); } DeclaredNamePtr lookupDeclaredName(TaggedParserAtomIndex name) { return declared_->lookup(name); } AddDeclaredNamePtr lookupDeclaredNameForAdd(TaggedParserAtomIndex name) { return declared_->lookupForAdd(name); } [[nodiscard]] bool addDeclaredName(ParseContext* pc, AddDeclaredNamePtr& p, TaggedParserAtomIndex name, DeclarationKind kind, uint32_t pos, ClosedOver closedOver = ClosedOver::No) { return maybeReportOOM( pc, declared_->add(p, name, DeclaredNameInfo(kind, pos, closedOver))); } // Add a FunctionBox as a possible candidate for Annex B.3.3 semantics. [[nodiscard]] bool addPossibleAnnexBFunctionBox(ParseContext* pc, FunctionBox* funbox); // Check if the candidate function boxes for Annex B.3.3 should in // fact get Annex B semantics. Checked on Scope exit. [[nodiscard]] bool propagateAndMarkAnnexBFunctionBoxes(ParseContext* pc, ParserBase* parser); // Add and remove catch parameter names. Used to implement the odd // semantics of catch bodies. bool addCatchParameters(ParseContext* pc, Scope& catchParamScope); void removeCatchParameters(ParseContext* pc, Scope& catchParamScope); void useAsVarScope(ParseContext* pc) { MOZ_ASSERT(!pc->varScope_); pc->varScope_ = this; } // Maximum number of fixed stack slots in a generator or async function // script. If a script would have more, we instead store some variables in // heap EnvironmentObjects. // // This limit is a performance heuristic. Stack slots reduce allocations, // and `Local` opcodes are a bit faster than `AliasedVar` ones; but at each // `yield` or `await` the stack slots must be memcpy'd into a // GeneratorObject. At some point the memcpy is too much. The limit is // plenty for typical human-authored code. static constexpr uint32_t FixedSlotLimit = 256; // This is called as we leave a function, var, or lexical scope in a // generator or async function. `ownSlotCount` is the number of `bindings_` // that are not closed over. void setOwnStackSlotCount(uint32_t ownSlotCount) { // Determine if this scope is too big to optimize bindings into stack // slots. The meaning of sizeBits_ changes from "maximum nested slot // count" to "UINT32_MAX if too big". uint32_t slotCount = ownSlotCount + sizeBits_; if (slotCount > FixedSlotLimit) { slotCount = sizeBits_; sizeBits_ = UINT32_MAX; } else { sizeBits_ = 0; } // Propagate total size to enclosing scope. if (Scope* parent = enclosing()) { if (slotCount > parent->sizeBits_) { parent->sizeBits_ = slotCount; } } } bool tooBigToOptimize() const { MOZ_ASSERT(sizeBits_ == 0 || sizeBits_ == UINT32_MAX, "call this only after the parser leaves the scope"); return sizeBits_ != 0; } // An iterator for the set of names a scope binds: the set of all // declared names for 'var' scopes, and the set of lexically declared // names, plus synthetic names, for non-'var' scopes. class BindingIter { friend class Scope; DeclaredNameMap::Range declaredRange_; mozilla::DebugOnly count_; bool isVarScope_; BindingIter(Scope& scope, bool isVarScope) : declaredRange_(scope.declared_->all()), count_(0), isVarScope_(isVarScope) { settle(); } bool isLexicallyDeclared() { return BindingKindIsLexical(kind()) || kind() == BindingKind::Synthetic || kind() == BindingKind::PrivateMethod; } void settle() { // Both var and lexically declared names are binding in a var // scope. if (isVarScope_) { return; } // Otherwise, only lexically declared names are binding. Pop the range // until we find such a name. while (!declaredRange_.empty()) { if (isLexicallyDeclared()) { break; } declaredRange_.popFront(); } } public: bool done() const { return declaredRange_.empty(); } explicit operator bool() const { return !done(); } TaggedParserAtomIndex name() { MOZ_ASSERT(!done()); return declaredRange_.front().key(); } DeclarationKind declarationKind() { MOZ_ASSERT(!done()); return declaredRange_.front().value()->kind(); } BindingKind kind() { return DeclarationKindToBindingKind(declarationKind()); } bool closedOver() { MOZ_ASSERT(!done()); return declaredRange_.front().value()->closedOver(); } void setClosedOver() { MOZ_ASSERT(!done()); return declaredRange_.front().value()->setClosedOver(); } void operator++(int) { MOZ_ASSERT(!done()); MOZ_ASSERT(count_ != UINT32_MAX); declaredRange_.popFront(); settle(); } }; inline BindingIter bindings(ParseContext* pc); }; class VarScope : public Scope { public: explicit inline VarScope(ParserBase* parser); explicit inline VarScope(FrontendContext* fc, ParseContext* pc, UsedNameTracker& usedNames); }; private: // Context shared between parsing and bytecode generation. SharedContext* sc_; // A mechanism used for error reporting. ErrorReporter& errorReporter_; // The innermost statement, i.e., top of the statement stack. Statement* innermostStatement_; // The innermost scope, i.e., top of the scope stack. // // The outermost scope in the stack is usually varScope_. In the case of // functions, the outermost scope is functionScope_, which may be // varScope_. See comment above functionScope_. Scope* innermostScope_; // If isFunctionBox() and the function is a named lambda, the DeclEnv // scope for named lambdas. mozilla::Maybe namedLambdaScope_; // If isFunctionBox(), the scope for the function. If there are no // parameter expressions, this is scope for the entire function. If there // are parameter expressions, this holds the special function names // ('.this', 'arguments') and the formal parameters. mozilla::Maybe functionScope_; // The body-level scope. This always exists, but not necessarily at the // beginning of parsing the script in the case of functions with parameter // expressions. Scope* varScope_; // Simple formal parameter names, in order of appearance. Only used when // isFunctionBox(). PooledVectorPtr positionalFormalParameterNames_; // Closed over binding names, in order of appearance. Null-delimited // between scopes. Only used when syntax parsing. PooledVectorPtr closedOverBindingsForLazy_; public: // All inner functions in this context. Only used when syntax parsing. // The Functions (or FunctionCreateionDatas) are traced as part of the // CompilationStencil function vector. Vector innerFunctionIndexesForLazy; // In a function context, points to a Directive struct that can be updated // to reflect new directives encountered in the Directive Prologue that // require reparsing the function. In global/module/generator-tail contexts, // we don't need to reparse when encountering a DirectivePrologue so this // pointer may be nullptr. Directives* newDirectives; // lastYieldOffset stores the offset of the last yield that was parsed. // NoYieldOffset is its initial value. static const uint32_t NoYieldOffset = UINT32_MAX; uint32_t lastYieldOffset; // lastAwaitOffset stores the offset of the last await that was parsed. // NoAwaitOffset is its initial value. static const uint32_t NoAwaitOffset = UINT32_MAX; uint32_t lastAwaitOffset; private: // Monotonically increasing id. uint32_t scriptId_; // Set when encountering a super.property inside a method. We need to mark // the nearest super scope as needing a home object. bool superScopeNeedsHomeObject_; public: ParseContext(FrontendContext* fc, ParseContext*& parent, SharedContext* sc, ErrorReporter& errorReporter, CompilationState& compilationState, Directives* newDirectives, bool isFull); [[nodiscard]] bool init(); SharedContext* sc() { return sc_; } // `true` if we are in the body of a function definition. bool isFunctionBox() const { return sc_->isFunctionBox(); } FunctionBox* functionBox() { return sc_->asFunctionBox(); } Statement* innermostStatement() { return innermostStatement_; } Scope* innermostScope() { // There is always at least one scope: the 'var' scope. MOZ_ASSERT(innermostScope_); return innermostScope_; } Scope& namedLambdaScope() { MOZ_ASSERT(functionBox()->isNamedLambda()); return *namedLambdaScope_; } Scope& functionScope() { MOZ_ASSERT(isFunctionBox()); return *functionScope_; } Scope& varScope() { MOZ_ASSERT(varScope_); return *varScope_; } bool isFunctionExtraBodyVarScopeInnermost() { return isFunctionBox() && functionBox()->hasParameterExprs && innermostScope() == varScope_; } template bool */> Statement* findInnermostStatement(Predicate predicate) { return Statement::findNearest(innermostStatement_, predicate); } template bool */> T* findInnermostStatement(Predicate predicate) { return Statement::findNearest(innermostStatement_, predicate); } template T* findInnermostStatement() { return Statement::findNearest(innermostStatement_); } AtomVector& positionalFormalParameterNames() { return *positionalFormalParameterNames_; } AtomVector& closedOverBindingsForLazy() { return *closedOverBindingsForLazy_; } enum class BreakStatementError : uint8_t { // Unlabeled break must be inside loop or switch. ToughBreak, LabelNotFound, }; // Return Err(true) if we have encountered at least one loop, // Err(false) otherwise. [[nodiscard]] inline JS::Result checkBreakStatement( TaggedParserAtomIndex label); enum class ContinueStatementError : uint8_t { NotInALoop, LabelNotFound, }; [[nodiscard]] inline JS::Result checkContinueStatement(TaggedParserAtomIndex label); // True if we are at the topmost level of a entire script or function body. // For example, while parsing this code we would encounter f1 and f2 at // body level, but we would not encounter f3 or f4 at body level: // // function f1() { function f2() { } } // if (cond) { function f3() { if (cond) { function f4() { } } } } // bool atBodyLevel() { return !innermostStatement_; } bool atGlobalLevel() { return atBodyLevel() && sc_->isGlobalContext(); } // True if we are at the topmost level of a module only. bool atModuleLevel() { return atBodyLevel() && sc_->isModuleContext(); } // True if we are at the topmost level of an entire script or module. For // example, in the comment on |atBodyLevel()| above, we would encounter |f1| // and the outermost |if (cond)| at top level, and everything else would not // be at top level. bool atTopLevel() { return atBodyLevel() && sc_->isTopLevelContext(); } bool atModuleTopLevel() { // True if we are at the topmost level of an entire module. // // For example, this is used to determine if an await statement should // mark a module as an async module during parsing. // // Example module: // import x from "y"; // // await x.foo(); // mark as Top level await. // // if (cond) { // await x.bar(); // mark as Top level await. // } // // async function z() { // await x.baz(); // do not mark as Top level await. // } return sc_->isModuleContext() && sc_->isTopLevelContext(); } // True if this is the outermost ParserContext for current compile. For // delazification, this lets us identify if the lazy PrivateScriptData is for // current parser context. bool isOutermostOfCurrentCompile() const { MOZ_ASSERT(!!enclosing() == !!scriptId()); return (scriptId() == 0); } void setSuperScopeNeedsHomeObject() { MOZ_ASSERT(sc_->allowSuperProperty()); superScopeNeedsHomeObject_ = true; } bool superScopeNeedsHomeObject() const { return superScopeNeedsHomeObject_; } bool useAsmOrInsideUseAsm() const { return sc_->isFunctionBox() && sc_->asFunctionBox()->useAsmOrInsideUseAsm(); } // A generator is marked as a generator before its body is parsed. GeneratorKind generatorKind() const { return sc_->isFunctionBox() ? sc_->asFunctionBox()->generatorKind() : GeneratorKind::NotGenerator; } bool isGenerator() const { return generatorKind() == GeneratorKind::Generator; } bool isAsync() const { return sc_->isSuspendableContext() && sc_->asSuspendableContext()->isAsync(); } bool isGeneratorOrAsync() const { return isGenerator() || isAsync(); } bool needsDotGeneratorName() const { return isGeneratorOrAsync(); } FunctionAsyncKind asyncKind() const { return isAsync() ? FunctionAsyncKind::AsyncFunction : FunctionAsyncKind::SyncFunction; } bool isArrowFunction() const { return sc_->isFunctionBox() && sc_->asFunctionBox()->isArrow(); } bool isMethod() const { return sc_->isFunctionBox() && sc_->asFunctionBox()->isMethod(); } bool isGetterOrSetter() const { return sc_->isFunctionBox() && (sc_->asFunctionBox()->isGetter() || sc_->asFunctionBox()->isSetter()); } bool allowReturn() const { return sc_->isFunctionBox() && sc_->asFunctionBox()->allowReturn(); } uint32_t scriptId() const { return scriptId_; } bool computeAnnexBAppliesToLexicalFunctionInInnermostScope( FunctionBox* funbox, ParserBase* parser, bool* annexBApplies); bool tryDeclareVar(TaggedParserAtomIndex name, ParserBase* parser, DeclarationKind kind, uint32_t beginPos, mozilla::Maybe* redeclaredKind, uint32_t* prevPos); bool hasUsedName(const UsedNameTracker& usedNames, TaggedParserAtomIndex name); bool hasUsedFunctionSpecialName(const UsedNameTracker& usedNames, TaggedParserAtomIndex name); bool declareFunctionThis(const UsedNameTracker& usedNames, bool canSkipLazyClosedOverBindings); bool declareFunctionArgumentsObject(const UsedNameTracker& usedNames, bool canSkipLazyClosedOverBindings); bool declareNewTarget(const UsedNameTracker& usedNames, bool canSkipLazyClosedOverBindings); bool declareDotGeneratorName(); bool declareTopLevelDotGeneratorName(); private: [[nodiscard]] bool isVarRedeclaredInInnermostScope( TaggedParserAtomIndex name, ParserBase* parser, DeclarationKind kind, mozilla::Maybe* out); [[nodiscard]] bool isVarRedeclaredInEval( TaggedParserAtomIndex name, ParserBase* parser, DeclarationKind kind, mozilla::Maybe* out); enum DryRunOption { NotDryRun, DryRunInnermostScopeOnly }; template bool tryDeclareVarHelper(TaggedParserAtomIndex name, ParserBase* parser, DeclarationKind kind, uint32_t beginPos, mozilla::Maybe* redeclaredKind, uint32_t* prevPos); }; } // namespace frontend } // namespace js #endif // frontend_ParseContext_h