/* -*- 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_SharedContext_h #define frontend_SharedContext_h #include "mozilla/Assertions.h" #include "mozilla/Attributes.h" #include "mozilla/Maybe.h" #include "jstypes.h" #include "frontend/AbstractScopePtr.h" // ScopeIndex #include "frontend/FunctionSyntaxKind.h" // FunctionSyntaxKind #include "frontend/ParseNode.h" #include "frontend/ScriptIndex.h" // ScriptIndex #include "js/WasmModule.h" // JS::WasmModule #include "vm/FunctionFlags.h" // js::FunctionFlags #include "vm/GeneratorAndAsyncKind.h" // js::GeneratorKind, js::FunctionAsyncKind #include "vm/JSFunction.h" #include "vm/JSScript.h" #include "vm/Scope.h" #include "vm/SharedStencil.h" namespace js { namespace frontend { struct CompilationStencil; struct CompilationState; class ParseContext; class ScriptStencil; struct ScopeContext; enum class StatementKind : uint8_t { Label, Block, If, Switch, With, Catch, Try, Finally, ForLoopLexicalHead, ForLoop, ForInLoop, ForOfLoop, DoLoop, WhileLoop, Class, // Used only by BytecodeEmitter. Spread, YieldStar, }; static inline bool StatementKindIsLoop(StatementKind kind) { return kind == StatementKind::ForLoop || kind == StatementKind::ForInLoop || kind == StatementKind::ForOfLoop || kind == StatementKind::DoLoop || kind == StatementKind::WhileLoop || kind == StatementKind::Spread || kind == StatementKind::YieldStar; } static inline bool StatementKindIsUnlabeledBreakTarget(StatementKind kind) { return StatementKindIsLoop(kind) || kind == StatementKind::Switch; } // List of directives that may be encountered in a Directive Prologue // (ES5 15.1). class Directives { bool strict_; bool asmJS_; public: explicit Directives(bool strict) : strict_(strict), asmJS_(false) {} explicit Directives(ParseContext* parent); void setStrict() { strict_ = true; } bool strict() const { return strict_; } void setAsmJS() { asmJS_ = true; } bool asmJS() const { return asmJS_; } Directives& operator=(Directives rhs) { strict_ = rhs.strict_; asmJS_ = rhs.asmJS_; return *this; } bool operator==(const Directives& rhs) const { return strict_ == rhs.strict_ && asmJS_ == rhs.asmJS_; } bool operator!=(const Directives& rhs) const { return !(*this == rhs); } }; // The kind of this-binding for the current scope. Note that arrow functions // have a lexical this-binding so their ThisBinding is the same as the // ThisBinding of their enclosing scope and can be any value. Derived // constructors require TDZ checks when accessing the binding. enum class ThisBinding : uint8_t { Global, Module, Function, DerivedConstructor }; // If Yes, the script inherits it's "this" environment and binding from the // enclosing script. This is true for arrow-functions and eval scripts. enum class InheritThis { No, Yes }; class GlobalSharedContext; class EvalSharedContext; class ModuleSharedContext; class SuspendableContext; #define FLAG_GETTER(enumName, enumEntry, lowerName, name) \ public: \ bool lowerName() const { return hasFlag(enumName::enumEntry); } #define FLAG_SETTER(enumName, enumEntry, lowerName, name) \ public: \ void set##name() { setFlag(enumName::enumEntry); } \ void set##name(bool b) { setFlag(enumName::enumEntry, b); } #define IMMUTABLE_FLAG_GETTER_SETTER(lowerName, name) \ FLAG_GETTER(ImmutableFlags, name, lowerName, name) \ FLAG_SETTER(ImmutableFlags, name, lowerName, name) #define IMMUTABLE_FLAG_GETTER(lowerName, name) \ FLAG_GETTER(ImmutableFlags, name, lowerName, name) /* * The struct SharedContext is part of the current parser context (see * ParseContext). It stores information that is reused between the parser and * the bytecode emitter. */ class SharedContext { public: JSContext* const cx_; protected: CompilationStencil& stencil_; // See: BaseScript::immutableFlags_ ImmutableScriptFlags immutableFlags_ = {}; // The location of this script in the source. Note that the value here differs // from the final BaseScript for the case of standalone functions. // This field is copied to ScriptStencil, and shouldn't be modified after the // copy. SourceExtent extent_ = {}; protected: // See: ThisBinding ThisBinding thisBinding_ = ThisBinding::Global; // These flags do not have corresponding script flags and may be inherited // from the scope chain in the case of eval and arrows. bool allowNewTarget_ : 1; bool allowSuperProperty_ : 1; bool allowSuperCall_ : 1; bool allowArguments_ : 1; bool inWith_ : 1; bool inClass_ : 1; // See `strict()` below. bool localStrict : 1; // True if "use strict"; appears in the body instead of being inherited. bool hasExplicitUseStrict_ : 1; // Tracks if script-related fields are already copied to ScriptStencil. // // If this field is true, those fileds shouldn't be modified. // // For FunctionBox, some fields are allowed to be modified, but the // modification should be synced with ScriptStencil by // FunctionBox::copyUpdated* methods. bool isScriptFieldCopiedToStencil : 1; // End of fields. enum class Kind : uint8_t { FunctionBox, Global, Eval, Module }; // Alias enum into SharedContext using ImmutableFlags = ImmutableScriptFlagsEnum; MOZ_MUST_USE bool hasFlag(ImmutableFlags flag) const { return immutableFlags_.hasFlag(flag); } void setFlag(ImmutableFlags flag, bool b = true) { MOZ_ASSERT(!isScriptFieldCopiedToStencil); immutableFlags_.setFlag(flag, b); } public: SharedContext(JSContext* cx, Kind kind, CompilationStencil& stencil, Directives directives, SourceExtent extent); IMMUTABLE_FLAG_GETTER_SETTER(isForEval, IsForEval) IMMUTABLE_FLAG_GETTER_SETTER(isModule, IsModule) IMMUTABLE_FLAG_GETTER_SETTER(isFunction, IsFunction) IMMUTABLE_FLAG_GETTER_SETTER(selfHosted, SelfHosted) IMMUTABLE_FLAG_GETTER_SETTER(forceStrict, ForceStrict) IMMUTABLE_FLAG_GETTER_SETTER(hasNonSyntacticScope, HasNonSyntacticScope) IMMUTABLE_FLAG_GETTER_SETTER(noScriptRval, NoScriptRval) IMMUTABLE_FLAG_GETTER(treatAsRunOnce, TreatAsRunOnce) // Strict: custom logic below IMMUTABLE_FLAG_GETTER_SETTER(hasModuleGoal, HasModuleGoal) IMMUTABLE_FLAG_GETTER_SETTER(hasInnerFunctions, HasInnerFunctions) IMMUTABLE_FLAG_GETTER_SETTER(hasDirectEval, HasDirectEval) IMMUTABLE_FLAG_GETTER_SETTER(bindingsAccessedDynamically, BindingsAccessedDynamically) IMMUTABLE_FLAG_GETTER_SETTER(hasCallSiteObj, HasCallSiteObj) const SourceExtent& extent() const { return extent_; } bool isFunctionBox() const { return isFunction(); } inline FunctionBox* asFunctionBox(); bool isModuleContext() const { return isModule(); } inline ModuleSharedContext* asModuleContext(); bool isSuspendableContext() const { return isFunction() || isModule(); } inline SuspendableContext* asSuspendableContext(); bool isGlobalContext() const { return !(isFunction() || isModule() || isForEval()); } inline GlobalSharedContext* asGlobalContext(); bool isEvalContext() const { return isForEval(); } inline EvalSharedContext* asEvalContext(); bool isTopLevelContext() const { return !isFunction(); } CompilationStencil& stencil() const { return stencil_; } ThisBinding thisBinding() const { return thisBinding_; } bool hasFunctionThisBinding() const { return thisBinding() == ThisBinding::Function || thisBinding() == ThisBinding::DerivedConstructor; } bool needsThisTDZChecks() const { return thisBinding() == ThisBinding::DerivedConstructor; } bool isSelfHosted() const { return selfHosted(); } bool allowNewTarget() const { return allowNewTarget_; } bool allowSuperProperty() const { return allowSuperProperty_; } bool allowSuperCall() const { return allowSuperCall_; } bool allowArguments() const { return allowArguments_; } bool inWith() const { return inWith_; } bool inClass() const { return inClass_; } bool hasExplicitUseStrict() const { return hasExplicitUseStrict_; } void setExplicitUseStrict() { hasExplicitUseStrict_ = true; } ImmutableScriptFlags immutableFlags() { return immutableFlags_; } bool allBindingsClosedOver() { return bindingsAccessedDynamically(); } // The ImmutableFlag tracks if the entire script is strict, while the // localStrict flag indicates the current region (such as class body) should // be treated as strict. The localStrict flag will always be reset to false // before the end of the script. bool strict() const { return hasFlag(ImmutableFlags::Strict) || localStrict; } void setStrictScript() { setFlag(ImmutableFlags::Strict); } bool setLocalStrictMode(bool strict) { bool retVal = localStrict; localStrict = strict; return retVal; } inline JSAtom* liftParserAtomToJSAtom(JSContext* cx, const ParserAtom* atomId); void copyScriptFields(ScriptStencil& script); void copyScriptExtraFields(ScriptStencilExtra& scriptExtra); }; class MOZ_STACK_CLASS GlobalSharedContext : public SharedContext { ScopeKind scopeKind_; public: GlobalScope::ParserData* bindings; GlobalSharedContext(JSContext* cx, ScopeKind scopeKind, CompilationStencil& stencil, Directives directives, SourceExtent extent); ScopeKind scopeKind() const { return scopeKind_; } }; inline GlobalSharedContext* SharedContext::asGlobalContext() { MOZ_ASSERT(isGlobalContext()); return static_cast(this); } class MOZ_STACK_CLASS EvalSharedContext : public SharedContext { public: EvalScope::ParserData* bindings; EvalSharedContext(JSContext* cx, CompilationStencil& stencil, CompilationState& compilationState, SourceExtent extent); }; inline EvalSharedContext* SharedContext::asEvalContext() { MOZ_ASSERT(isEvalContext()); return static_cast(this); } enum class HasHeritage { No, Yes }; class SuspendableContext : public SharedContext { public: SuspendableContext(JSContext* cx, Kind kind, CompilationStencil& stencil, Directives directives, SourceExtent extent, bool isGenerator, bool isAsync); IMMUTABLE_FLAG_GETTER_SETTER(isAsync, IsAsync) IMMUTABLE_FLAG_GETTER_SETTER(isGenerator, IsGenerator) bool needsFinalYield() const { return isGenerator() || isAsync(); } bool needsDotGeneratorName() const { return isGenerator() || isAsync(); } bool needsClearSlotsOnExit() const { return isGenerator() || isAsync(); } bool needsIteratorResult() const { return isGenerator() && !isAsync(); } bool needsPromiseResult() const { return isAsync() && !isGenerator(); } }; class FunctionBox : public SuspendableContext { friend struct GCThingList; CompilationState& compilationState_; // If this FunctionBox refers to a lazy child of the function being // compiled, this field holds the child's immediately enclosing scope's index. // Once compilation succeeds, we will store the scope pointed by this in the // child's BaseScript. (Debugger may become confused if lazy scripts refer to // partially initialized enclosing scopes, so we must avoid storing the // scope in the BaseScript until compilation has completed // successfully.) // This is copied to ScriptStencil. // Any update after the copy should be synced to the ScriptStencil. mozilla::Maybe enclosingScopeIndex_; // Names from the named lambda scope, if a named lambda. LexicalScope::ParserData* namedLambdaBindings_ = nullptr; // Names from the function scope. FunctionScope::ParserData* functionScopeBindings_ = nullptr; // Names from the extra 'var' scope of the function, if the parameter list // has expressions. VarScope::ParserData* extraVarScopeBindings_ = nullptr; // The explicit or implicit name of the function. The FunctionFlags indicate // the kind of name. // This is copied to ScriptStencil. // Any update after the copy should be synced to the ScriptStencil. const ParserAtom* atom_ = nullptr; // Index into BaseCompilationStencil::scriptData. ScriptIndex funcDataIndex_ = ScriptIndex(-1); // See: FunctionFlags // This is copied to ScriptStencil. // Any update after the copy should be synced to the ScriptStencil. FunctionFlags flags_ = {}; // See: ImmutableScriptData::funLength uint16_t length_ = 0; // JSFunction::nargs_ // This field is copied to ScriptStencil, and shouldn't be modified after the // copy. uint16_t nargs_ = 0; // See: PrivateScriptData::memberInitializers_ // This field is copied to ScriptStencil, and shouldn't be modified after the // copy. mozilla::Maybe memberInitializers_ = {}; public: // Back pointer used by asm.js for error messages. FunctionNode* functionNode = nullptr; // True if bytecode will be emitted for this function in the current // compilation. bool emitBytecode : 1; // This is set by the BytecodeEmitter of the enclosing script when a reference // to this function is generated. This is also used to determine a hoisted // function already is referenced by the bytecode. bool wasEmitted_ : 1; // Need to emit a synthesized Annex B assignment bool isAnnexB : 1; // Track if we saw "use asm". // If we successfully validated it, `flags_` is seto to `AsmJS` kind. bool useAsm : 1; // Analysis of parameter list bool hasParameterExprs : 1; bool hasDestructuringArgs : 1; bool hasDuplicateParameters : 1; // Arrow function with expression body like: `() => 1`. bool hasExprBody_ : 1; // Tracks if function-related fields are already copied to ScriptStencil. // If this field is true, modification to those fields should be synced with // ScriptStencil by copyUpdated* methods. bool isFunctionFieldCopiedToStencil : 1; // End of fields. FunctionBox(JSContext* cx, SourceExtent extent, CompilationStencil& stencil, CompilationState& compilationState, Directives directives, GeneratorKind generatorKind, FunctionAsyncKind asyncKind, const ParserAtom* atom, FunctionFlags flags, ScriptIndex index); ScriptStencil& functionStencil() const; ScriptStencilExtra& functionExtraStencil() const; bool hasFunctionExtraStencil() const; LexicalScope::ParserData* namedLambdaBindings() { return namedLambdaBindings_; } void setNamedLambdaBindings(LexicalScope::ParserData* bindings) { namedLambdaBindings_ = bindings; } FunctionScope::ParserData* functionScopeBindings() { return functionScopeBindings_; } void setFunctionScopeBindings(FunctionScope::ParserData* bindings) { functionScopeBindings_ = bindings; } VarScope::ParserData* extraVarScopeBindings() { return extraVarScopeBindings_; } void setExtraVarScopeBindings(VarScope::ParserData* bindings) { extraVarScopeBindings_ = bindings; } void initFromLazyFunction(JSFunction* fun); void initStandalone(ScopeContext& scopeContext, FunctionFlags flags, FunctionSyntaxKind kind); void initWithEnclosingParseContext(ParseContext* enclosing, FunctionFlags flags, FunctionSyntaxKind kind); void setEnclosingScopeForInnerLazyFunction(ScopeIndex scopeIndex); bool wasEmitted() const { return wasEmitted_; } void setWasEmitted(bool wasEmitted) { wasEmitted_ = wasEmitted; if (isFunctionFieldCopiedToStencil) { copyUpdatedWasEmitted(); } } MOZ_MUST_USE bool setAsmJSModule(const JS::WasmModule* module); bool isAsmJSModule() const { return flags_.isAsmJSNative(); } bool hasEnclosingScopeIndex() const { return enclosingScopeIndex_.isSome(); } ScopeIndex getEnclosingScopeIndex() const { return *enclosingScopeIndex_; } IMMUTABLE_FLAG_GETTER_SETTER(isAsync, IsAsync) IMMUTABLE_FLAG_GETTER_SETTER(isGenerator, IsGenerator) IMMUTABLE_FLAG_GETTER_SETTER(funHasExtensibleScope, FunHasExtensibleScope) IMMUTABLE_FLAG_GETTER_SETTER(functionHasThisBinding, FunctionHasThisBinding) // NeedsHomeObject: custom logic below. // IsDerivedClassConstructor: custom logic below. // IsFieldInitializer: custom logic below. IMMUTABLE_FLAG_GETTER_SETTER(hasRest, HasRest) IMMUTABLE_FLAG_GETTER_SETTER(needsFunctionEnvironmentObjects, NeedsFunctionEnvironmentObjects) IMMUTABLE_FLAG_GETTER_SETTER(functionHasExtraBodyVarScope, FunctionHasExtraBodyVarScope) IMMUTABLE_FLAG_GETTER_SETTER(shouldDeclareArguments, ShouldDeclareArguments) IMMUTABLE_FLAG_GETTER_SETTER(argumentsHasVarBinding, ArgumentsHasVarBinding) // AlwaysNeedsArgsObj: custom logic below. // HasMappedArgsObj: custom logic below. bool needsCallObjectRegardlessOfBindings() const { // Always create a CallObject if: // - The scope is extensible at runtime due to sloppy eval. // - The function is a generator or async function. (The debugger reads the // generator object directly from the frame.) return funHasExtensibleScope() || isGenerator() || isAsync(); } bool needsExtraBodyVarEnvironmentRegardlessOfBindings() const { MOZ_ASSERT(hasParameterExprs); return funHasExtensibleScope(); } GeneratorKind generatorKind() const { return isGenerator() ? GeneratorKind::Generator : GeneratorKind::NotGenerator; } FunctionAsyncKind asyncKind() const { return isAsync() ? FunctionAsyncKind::AsyncFunction : FunctionAsyncKind::SyncFunction; } bool needsFinalYield() const { return isGenerator() || isAsync(); } bool needsDotGeneratorName() const { return isGenerator() || isAsync(); } bool needsClearSlotsOnExit() const { return isGenerator() || isAsync(); } bool needsIteratorResult() const { return isGenerator() && !isAsync(); } bool needsPromiseResult() const { return isAsync() && !isGenerator(); } bool isArrow() const { return flags_.isArrow(); } bool isLambda() const { return flags_.isLambda(); } bool hasExprBody() const { return hasExprBody_; } void setHasExprBody() { MOZ_ASSERT(isArrow()); hasExprBody_ = true; } bool isNamedLambda() const { return flags_.isNamedLambda(explicitName() != nullptr); } bool isGetter() const { return flags_.isGetter(); } bool isSetter() const { return flags_.isSetter(); } bool isMethod() const { return flags_.isMethod(); } bool isClassConstructor() const { return flags_.isClassConstructor(); } bool isInterpreted() const { return flags_.hasBaseScript(); } FunctionFlags::FunctionKind kind() { return flags_.kind(); } bool hasInferredName() const { return flags_.hasInferredName(); } bool hasGuessedAtom() const { return flags_.hasGuessedAtom(); } const ParserAtom* displayAtom() const { return atom_; } const ParserAtom* explicitName() const { return (hasInferredName() || hasGuessedAtom()) ? nullptr : atom_; } // NOTE: We propagate to any existing functions for now. This handles both the // delazification case where functions already exist, and also handles // code-coverage which is not yet deferred. void setInferredName(const ParserAtom* atom) { atom_ = atom; flags_.setInferredName(); if (isFunctionFieldCopiedToStencil) { copyUpdatedAtomAndFlags(); } } void setGuessedAtom(const ParserAtom* atom) { atom_ = atom; flags_.setGuessedAtom(); if (isFunctionFieldCopiedToStencil) { copyUpdatedAtomAndFlags(); } } void setAlwaysNeedsArgsObj() { MOZ_ASSERT(argumentsHasVarBinding()); setFlag(ImmutableFlags::AlwaysNeedsArgsObj); } bool needsHomeObject() const { return hasFlag(ImmutableFlags::NeedsHomeObject); } void setNeedsHomeObject() { MOZ_ASSERT(flags_.allowSuperProperty()); setFlag(ImmutableFlags::NeedsHomeObject); } bool isDerivedClassConstructor() const { return hasFlag(ImmutableFlags::IsDerivedClassConstructor); } void setDerivedClassConstructor() { MOZ_ASSERT(flags_.isClassConstructor()); setFlag(ImmutableFlags::IsDerivedClassConstructor); } bool isFieldInitializer() const { return hasFlag(ImmutableFlags::IsFieldInitializer); } void setFieldInitializer() { MOZ_ASSERT(flags_.isMethod()); setFlag(ImmutableFlags::IsFieldInitializer); } bool hasSimpleParameterList() const { return !hasRest() && !hasParameterExprs && !hasDestructuringArgs; } bool hasMappedArgsObj() const { return !strict() && hasSimpleParameterList(); } // Return whether this or an enclosing function is being parsed and // validated as asm.js. Note: if asm.js validation fails, this will be false // while the function is being reparsed. This flag can be used to disable // certain parsing features that are necessary in general, but unnecessary // for validated asm.js. bool useAsmOrInsideUseAsm() const { return useAsm; } void setStart(uint32_t offset, uint32_t line, uint32_t column) { MOZ_ASSERT(!isScriptFieldCopiedToStencil); extent_.sourceStart = offset; extent_.lineno = line; extent_.column = column; } void setEnd(uint32_t end) { MOZ_ASSERT(!isScriptFieldCopiedToStencil); // For all functions except class constructors, the buffer and // toString ending positions are the same. Class constructors override // the toString ending position with the end of the class definition. extent_.sourceEnd = end; extent_.toStringEnd = end; } void setCtorToStringEnd(uint32_t end) { extent_.toStringEnd = end; if (isScriptFieldCopiedToStencil) { copyUpdatedExtent(); } } void setCtorFunctionHasThisBinding() { immutableFlags_.setFlag(ImmutableFlags::FunctionHasThisBinding, true); if (isScriptFieldCopiedToStencil) { copyUpdatedImmutableFlags(); } } uint16_t length() { return length_; } void setLength(uint16_t length) { length_ = length; } void setArgCount(uint16_t args) { MOZ_ASSERT(!isFunctionFieldCopiedToStencil); nargs_ = args; } size_t nargs() { return nargs_; } bool hasMemberInitializers() const { return memberInitializers_.isSome(); } const MemberInitializers& memberInitializers() const { return *memberInitializers_; } void setMemberInitializers(MemberInitializers memberInitializers) { MOZ_ASSERT(memberInitializers_.isNothing()); memberInitializers_ = mozilla::Some(memberInitializers); if (isScriptFieldCopiedToStencil) { copyUpdatedMemberInitializers(); } } ScriptIndex index() { return funcDataIndex_; } void finishScriptFlags(); void copyScriptFields(ScriptStencil& script); void copyFunctionFields(ScriptStencil& script); void copyFunctionExtraFields(ScriptStencilExtra& scriptExtra); // * setCtorFunctionHasThisBinding can be called to a class constructor // with a lazy function, while parsing enclosing class void copyUpdatedImmutableFlags(); // * setCtorToStringEnd bcan be called to a class constructor with a lazy // function, while parsing enclosing class void copyUpdatedExtent(); // * setMemberInitializers can be called to a class constructor with a lazy // function, while emitting enclosing script void copyUpdatedMemberInitializers(); // * setEnclosingScopeForInnerLazyFunction can be called to a lazy function, // while emitting enclosing script void copyUpdatedEnclosingScopeIndex(); // * setInferredName can be called to a lazy function, while emitting // enclosing script // * setGuessedAtom can be called to both lazy/non-lazy functions, // while running NameFunctions void copyUpdatedAtomAndFlags(); // * setWasEmitted can be called to a lazy function, while emitting // enclosing script void copyUpdatedWasEmitted(); }; #undef FLAG_GETTER_SETTER #undef IMMUTABLE_FLAG_GETTER_SETTER inline FunctionBox* SharedContext::asFunctionBox() { MOZ_ASSERT(isFunctionBox()); return static_cast(this); } inline SuspendableContext* SharedContext::asSuspendableContext() { MOZ_ASSERT(isSuspendableContext()); return static_cast(this); } } // namespace frontend } // namespace js #endif /* frontend_SharedContext_h */