/* -*- 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 script descriptor. */ #ifndef jsscript_h #define jsscript_h #include "mozilla/Atomics.h" #include "mozilla/MemoryReporting.h" #include "mozilla/PodOperations.h" #include "mozilla/Variant.h" #include "jsatom.h" #include "jsopcode.h" #include "jstypes.h" #include "frontend/NameAnalysisTypes.h" #include "gc/Barrier.h" #include "gc/Rooting.h" #include "jit/IonCode.h" #include "js/UbiNode.h" #include "js/UniquePtr.h" #include "vm/NativeObject.h" #include "vm/Scope.h" #include "vm/Shape.h" #include "vm/SharedImmutableStringsCache.h" namespace JS { struct ScriptSourceInfo; } // namespace JS namespace js { namespace jit { struct BaselineScript; struct IonScriptCounts; } // namespace jit # define ION_DISABLED_SCRIPT ((js::jit::IonScript*)0x1) # define ION_COMPILING_SCRIPT ((js::jit::IonScript*)0x2) # define ION_PENDING_SCRIPT ((js::jit::IonScript*)0x3) # define BASELINE_DISABLED_SCRIPT ((js::jit::BaselineScript*)0x1) class BreakpointSite; class Debugger; class LazyScript; class ModuleObject; class RegExpObject; struct SourceCompressionTask; class Shape; namespace frontend { struct BytecodeEmitter; class FunctionBox; class ModuleSharedContext; } // namespace frontend namespace detail { // Do not call this directly! It is exposed for the friend declarations in // this file. bool CopyScript(JSContext* cx, HandleScript src, HandleScript dst, MutableHandle<GCVector<Scope*>> scopes); } // namespace detail } // namespace js /* * Type of try note associated with each catch or finally block, and also with * for-in and other kinds of loops. Non-for-in loops do not need these notes * for exception unwinding, but storing their boundaries here is helpful for * heuristics that need to know whether a given op is inside a loop. */ enum JSTryNoteKind { JSTRY_CATCH, JSTRY_FINALLY, JSTRY_FOR_IN, JSTRY_FOR_OF, JSTRY_LOOP }; /* * Exception handling record. */ struct JSTryNote { uint8_t kind; /* one of JSTryNoteKind */ uint32_t stackDepth; /* stack depth upon exception handler entry */ uint32_t start; /* start of the try statement or loop relative to script->main */ uint32_t length; /* length of the try statement or loop */ }; namespace js { // A block scope has a range in bytecode: it is entered at some offset, and left // at some later offset. Scopes can be nested. Given an offset, the // ScopeNote containing that offset whose with the highest start value // indicates the block scope. The block scope list is sorted by increasing // start value. // // It is possible to leave a scope nonlocally, for example via a "break" // statement, so there may be short bytecode ranges in a block scope in which we // are popping the block chain in preparation for a goto. These exits are also // nested with respect to outer scopes. The scopes in these exits are indicated // by the "index" field, just like any other block. If a nonlocal exit pops the // last block scope, the index will be NoScopeIndex. // struct ScopeNote { // Sentinel index for no Scope. static const uint32_t NoScopeIndex = UINT32_MAX; // Sentinel index for no ScopeNote. static const uint32_t NoScopeNoteIndex = UINT32_MAX; uint32_t index; // Index of Scope in the scopes array, or // NoScopeIndex if there is no block scope in // this range. uint32_t start; // Bytecode offset at which this scope starts, // from script->main(). uint32_t length; // Bytecode length of scope. uint32_t parent; // Index of parent block scope in notes, or NoScopeNote. }; struct ConstArray { js::GCPtrValue* vector; // array of indexed constant values uint32_t length; }; struct ObjectArray { js::GCPtrObject* vector; // Array of indexed objects. uint32_t length; // Count of indexed objects. }; struct ScopeArray { js::GCPtrScope* vector; // Array of indexed scopes. uint32_t length; // Count of indexed scopes. }; struct TryNoteArray { JSTryNote* vector; // Array of indexed try notes. uint32_t length; // Count of indexed try notes. }; struct ScopeNoteArray { ScopeNote* vector; // Array of indexed ScopeNote records. uint32_t length; // Count of indexed try notes. }; class YieldOffsetArray { friend bool detail::CopyScript(JSContext* cx, HandleScript src, HandleScript dst, MutableHandle<GCVector<Scope*>> scopes); uint32_t* vector_; // Array of bytecode offsets. uint32_t length_; // Count of bytecode offsets. public: void init(uint32_t* vector, uint32_t length) { vector_ = vector; length_ = length; } uint32_t& operator[](uint32_t index) { MOZ_ASSERT(index < length_); return vector_[index]; } uint32_t length() const { return length_; } }; class ScriptCounts { public: typedef mozilla::Vector<PCCounts, 0, SystemAllocPolicy> PCCountsVector; inline ScriptCounts(); inline explicit ScriptCounts(PCCountsVector&& jumpTargets); inline ScriptCounts(ScriptCounts&& src); inline ~ScriptCounts(); inline ScriptCounts& operator=(ScriptCounts&& src); // Return the counter used to count the number of visits. Returns null if // the element is not found. PCCounts* maybeGetPCCounts(size_t offset); const PCCounts* maybeGetPCCounts(size_t offset) const; // PCCounts are stored at jump-target offsets. This function looks for the // previous PCCount which is in the same basic block as the current offset. PCCounts* getImmediatePrecedingPCCounts(size_t offset); // Return the counter used to count the number of throws. Returns null if // the element is not found. const PCCounts* maybeGetThrowCounts(size_t offset) const; // Throw counts are stored at the location of each throwing // instruction. This function looks for the previous throw count. // // Note: if the offset of the returned count is higher than the offset of // the immediate preceding PCCount, then this throw happened in the same // basic block. const PCCounts* getImmediatePrecedingThrowCounts(size_t offset) const; // Return the counter used to count the number of throws. Allocate it if // none exists yet. Returns null if the allocation failed. PCCounts* getThrowCounts(size_t offset); private: friend class ::JSScript; friend struct ScriptAndCounts; // This sorted array is used to map an offset to the number of times a // branch got visited. PCCountsVector pcCounts_; // This sorted vector is used to map an offset to the number of times an // instruction throw. PCCountsVector throwCounts_; // Information about any Ion compilations for the script. jit::IonScriptCounts* ionCounts_; }; // Note: The key of this hash map is a weak reference to a JSScript. We do not // use the WeakMap implementation provided in jsweakmap.h because it would be // collected at the beginning of the sweeping of the compartment, thus before // the calls to the JSScript::finalize function which are used to aggregate code // coverage results on the compartment. typedef HashMap<JSScript*, ScriptCounts*, DefaultHasher<JSScript*>, SystemAllocPolicy> ScriptCountsMap; class DebugScript { friend class ::JSScript; friend struct ::JSCompartment; /* * When non-zero, compile script in single-step mode. The top bit is set and * cleared by setStepMode, as used by JSD. The lower bits are a count, * adjusted by changeStepModeCount, used by the Debugger object. Only * when the bit is clear and the count is zero may we compile the script * without single-step support. */ uint32_t stepMode; /* * Number of breakpoint sites at opcodes in the script. This is the number * of populated entries in DebugScript::breakpoints, below. */ uint32_t numSites; /* * Breakpoints set in our script. For speed and simplicity, this array is * parallel to script->code(): the BreakpointSite for the opcode at * script->code()[offset] is debugScript->breakpoints[offset]. Naturally, * this array's true length is script->length(). */ BreakpointSite* breakpoints[1]; }; typedef HashMap<JSScript*, DebugScript*, DefaultHasher<JSScript*>, SystemAllocPolicy> DebugScriptMap; class ScriptSource; struct ScriptSourceChunk { ScriptSource* ss; uint32_t chunk; ScriptSourceChunk() : ss(nullptr), chunk(0) {} ScriptSourceChunk(ScriptSource* ss, uint32_t chunk) : ss(ss), chunk(chunk) { MOZ_ASSERT(valid());; } bool valid() const { return ss != nullptr; } bool operator==(const ScriptSourceChunk& other) const { return ss == other.ss && chunk == other.chunk; } }; struct ScriptSourceChunkHasher { using Lookup = ScriptSourceChunk; static HashNumber hash(const ScriptSourceChunk& ssc) { return mozilla::AddToHash(DefaultHasher<ScriptSource*>::hash(ssc.ss), ssc.chunk); } static bool match(const ScriptSourceChunk& c1, const ScriptSourceChunk& c2) { return c1 == c2; } }; class UncompressedSourceCache { typedef HashMap<ScriptSourceChunk, UniqueTwoByteChars, ScriptSourceChunkHasher, SystemAllocPolicy> Map; public: // Hold an entry in the source data cache and prevent it from being purged on GC. class AutoHoldEntry { UncompressedSourceCache* cache_; ScriptSourceChunk sourceChunk_; UniqueTwoByteChars charsToFree_; public: explicit AutoHoldEntry(); ~AutoHoldEntry(); void holdChars(UniqueTwoByteChars chars); private: void holdEntry(UncompressedSourceCache* cache, const ScriptSourceChunk& sourceChunk); void deferDelete(UniqueTwoByteChars chars); const ScriptSourceChunk& sourceChunk() const { return sourceChunk_; } friend class UncompressedSourceCache; }; private: UniquePtr<Map> map_; AutoHoldEntry* holder_; public: UncompressedSourceCache() : holder_(nullptr) {} const char16_t* lookup(const ScriptSourceChunk& ssc, AutoHoldEntry& asp); bool put(const ScriptSourceChunk& ssc, UniqueTwoByteChars chars, AutoHoldEntry& asp); void purge(); size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf); private: void holdEntry(AutoHoldEntry& holder, const ScriptSourceChunk& ssc); void releaseEntry(AutoHoldEntry& holder); }; class ScriptSource { friend struct SourceCompressionTask; uint32_t refs; // Note: while ScriptSources may be compressed off thread, they are only // modified by the main thread, and all members are always safe to access // on the main thread. // Indicate which field in the |data| union is active. struct Missing { }; struct Uncompressed { SharedImmutableTwoByteString string; explicit Uncompressed(SharedImmutableTwoByteString&& str) : string(mozilla::Move(str)) { } }; struct Compressed { SharedImmutableString raw; size_t uncompressedLength; Compressed(SharedImmutableString&& raw, size_t uncompressedLength) : raw(mozilla::Move(raw)) , uncompressedLength(uncompressedLength) { } }; using SourceType = mozilla::Variant<Missing, Uncompressed, Compressed>; SourceType data; // The filename of this script. UniqueChars filename_; UniqueTwoByteChars displayURL_; UniqueTwoByteChars sourceMapURL_; bool mutedErrors_; // bytecode offset in caller script that generated this code. // This is present for eval-ed code, as well as "new Function(...)"-introduced // scripts. uint32_t introductionOffset_; // If this ScriptSource was generated by a code-introduction mechanism such // as |eval| or |new Function|, the debugger needs access to the "raw" // filename of the top-level script that contains the eval-ing code. To // keep track of this, we must preserve the original outermost filename (of // the original introducer script), so that instead of a filename of // "foo.js line 30 > eval line 10 > Function", we can obtain the original // raw filename of "foo.js". // // In the case described above, this field will be non-null and will be the // original raw filename from above. Otherwise this field will be null. UniqueChars introducerFilename_; // A string indicating how this source code was introduced into the system. // This accessor returns one of the following values: // "eval" for code passed to |eval|. // "Function" for code passed to the |Function| constructor. // "Worker" for code loaded by calling the Web worker constructor—the worker's main script. // "importScripts" for code by calling |importScripts| in a web worker. // "handler" for code assigned to DOM elements' event handler IDL attributes. // "scriptElement" for code belonging to <script> elements. // undefined if the implementation doesn't know how the code was introduced. // This is a constant, statically allocated C string, so does not need // memory management. const char* introductionType_; // True if we can call JSRuntime::sourceHook to load the source on // demand. If sourceRetrievable_ and hasSourceData() are false, it is not // possible to get source at all. bool sourceRetrievable_:1; bool argumentsNotIncluded_:1; bool hasIntroductionOffset_:1; const char16_t* chunkChars(JSContext* cx, UncompressedSourceCache::AutoHoldEntry& holder, size_t chunk); public: explicit ScriptSource() : refs(0), data(SourceType(Missing())), filename_(nullptr), displayURL_(nullptr), sourceMapURL_(nullptr), mutedErrors_(false), introductionOffset_(0), introducerFilename_(nullptr), introductionType_(nullptr), sourceRetrievable_(false), argumentsNotIncluded_(false), hasIntroductionOffset_(false) { } ~ScriptSource() { MOZ_ASSERT(refs == 0); } void incref() { refs++; } void decref() { MOZ_ASSERT(refs != 0); if (--refs == 0) js_delete(this); } bool initFromOptions(ExclusiveContext* cx, const ReadOnlyCompileOptions& options); bool setSourceCopy(ExclusiveContext* cx, JS::SourceBufferHolder& srcBuf, bool argumentsNotIncluded, SourceCompressionTask* tok); void setSourceRetrievable() { sourceRetrievable_ = true; } bool sourceRetrievable() const { return sourceRetrievable_; } bool hasSourceData() const { return !data.is<Missing>(); } bool hasCompressedSource() const { return data.is<Compressed>(); } size_t length() const { struct LengthMatcher { size_t match(const Uncompressed& u) { return u.string.length(); } size_t match(const Compressed& c) { return c.uncompressedLength; } size_t match(const Missing& m) { MOZ_CRASH("ScriptSource::length on a missing source"); return 0; } }; MOZ_ASSERT(hasSourceData()); return data.match(LengthMatcher()); } bool argumentsNotIncluded() const { MOZ_ASSERT(hasSourceData()); return argumentsNotIncluded_; } // Return a string containing the chars starting at |begin| and ending at // |begin + len|. const char16_t* chars(JSContext* cx, UncompressedSourceCache::AutoHoldEntry& asp, size_t begin, size_t len); JSFlatString* substring(JSContext* cx, size_t start, size_t stop); JSFlatString* substringDontDeflate(JSContext* cx, size_t start, size_t stop); void addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, JS::ScriptSourceInfo* info) const; MOZ_MUST_USE bool setSource(ExclusiveContext* cx, mozilla::UniquePtr<char16_t[], JS::FreePolicy>&& source, size_t length); void setSource(SharedImmutableTwoByteString&& string); MOZ_MUST_USE bool setCompressedSource( ExclusiveContext* cx, mozilla::UniquePtr<char[], JS::FreePolicy>&& raw, size_t rawLength, size_t sourceLength); void setCompressedSource(SharedImmutableString&& raw, size_t sourceLength); // XDR handling template <XDRMode mode> bool performXDR(XDRState<mode>* xdr); bool setFilename(ExclusiveContext* cx, const char* filename); const char* introducerFilename() const { return introducerFilename_ ? introducerFilename_.get() : filename_.get(); } bool hasIntroductionType() const { return introductionType_; } const char* introductionType() const { MOZ_ASSERT(hasIntroductionType()); return introductionType_; } const char* filename() const { return filename_.get(); } // Display URLs bool setDisplayURL(ExclusiveContext* cx, const char16_t* displayURL); bool hasDisplayURL() const { return displayURL_ != nullptr; } const char16_t * displayURL() { MOZ_ASSERT(hasDisplayURL()); return displayURL_.get(); } // Source maps bool setSourceMapURL(ExclusiveContext* cx, const char16_t* sourceMapURL); bool hasSourceMapURL() const { return sourceMapURL_ != nullptr; } const char16_t * sourceMapURL() { MOZ_ASSERT(hasSourceMapURL()); return sourceMapURL_.get(); } bool mutedErrors() const { return mutedErrors_; } bool hasIntroductionOffset() const { return hasIntroductionOffset_; } uint32_t introductionOffset() const { MOZ_ASSERT(hasIntroductionOffset()); return introductionOffset_; } void setIntroductionOffset(uint32_t offset) { MOZ_ASSERT(!hasIntroductionOffset()); MOZ_ASSERT(offset <= (uint32_t)INT32_MAX); introductionOffset_ = offset; hasIntroductionOffset_ = true; } }; class ScriptSourceHolder { ScriptSource* ss; public: ScriptSourceHolder() : ss(nullptr) {} explicit ScriptSourceHolder(ScriptSource* ss) : ss(ss) { ss->incref(); } ~ScriptSourceHolder() { if (ss) ss->decref(); } void reset(ScriptSource* newss) { if (ss) ss->decref(); ss = newss; ss->incref(); } ScriptSource* get() const { return ss; } }; class ScriptSourceObject : public NativeObject { static const ClassOps classOps_; public: static const Class class_; static void trace(JSTracer* trc, JSObject* obj); static void finalize(FreeOp* fop, JSObject* obj); static ScriptSourceObject* create(ExclusiveContext* cx, ScriptSource* source); // Initialize those properties of this ScriptSourceObject whose values // are provided by |options|, re-wrapping as necessary. static bool initFromOptions(JSContext* cx, HandleScriptSource source, const ReadOnlyCompileOptions& options); ScriptSource* source() const { return static_cast<ScriptSource*>(getReservedSlot(SOURCE_SLOT).toPrivate()); } JSObject* element() const { return getReservedSlot(ELEMENT_SLOT).toObjectOrNull(); } const Value& elementAttributeName() const { MOZ_ASSERT(!getReservedSlot(ELEMENT_PROPERTY_SLOT).isMagic()); return getReservedSlot(ELEMENT_PROPERTY_SLOT); } JSScript* introductionScript() const { if (getReservedSlot(INTRODUCTION_SCRIPT_SLOT).isUndefined()) return nullptr; void* untyped = getReservedSlot(INTRODUCTION_SCRIPT_SLOT).toPrivate(); MOZ_ASSERT(untyped); return static_cast<JSScript*>(untyped); } private: static const uint32_t SOURCE_SLOT = 0; static const uint32_t ELEMENT_SLOT = 1; static const uint32_t ELEMENT_PROPERTY_SLOT = 2; static const uint32_t INTRODUCTION_SCRIPT_SLOT = 3; static const uint32_t RESERVED_SLOTS = 4; }; enum GeneratorKind { NotGenerator, LegacyGenerator, StarGenerator }; enum FunctionAsyncKind { SyncFunction, AsyncFunction }; static inline unsigned GeneratorKindAsBits(GeneratorKind generatorKind) { return static_cast<unsigned>(generatorKind); } static inline GeneratorKind GeneratorKindFromBits(unsigned val) { MOZ_ASSERT(val <= StarGenerator); return static_cast<GeneratorKind>(val); } static inline unsigned AsyncKindAsBits(FunctionAsyncKind asyncKind) { return static_cast<unsigned>(asyncKind); } static inline FunctionAsyncKind AsyncKindFromBits(unsigned val) { MOZ_ASSERT(val <= AsyncFunction); return static_cast<FunctionAsyncKind>(val); } /* * NB: after a successful XDR_DECODE, XDRScript callers must do any required * subsequent set-up of owning function or script object and then call * CallNewScriptHook. */ template<XDRMode mode> bool XDRScript(XDRState<mode>* xdr, HandleScope enclosingScope, HandleScript enclosingScript, HandleFunction fun, MutableHandleScript scriptp); template<XDRMode mode> bool XDRLazyScript(XDRState<mode>* xdr, HandleScope enclosingScope, HandleScript enclosingScript, HandleFunction fun, MutableHandle<LazyScript*> lazy); /* * Code any constant value. */ template<XDRMode mode> bool XDRScriptConst(XDRState<mode>* xdr, MutableHandleValue vp); /* * Common data that can be shared between many scripts in a single runtime. */ class SharedScriptData { // This class is reference counted as follows: each pointer from a JSScript // counts as one reference plus there may be one reference from the shared // script data table. mozilla::Atomic<uint32_t> refCount_; uint32_t dataLength_; uint32_t natoms_; uint32_t codeLength_; uintptr_t data_[1]; public: static SharedScriptData* new_(ExclusiveContext* cx, uint32_t codeLength, uint32_t srcnotesLength, uint32_t natoms); uint32_t refCount() const { return refCount_; } void incRefCount() { refCount_++; } void decRefCount() { MOZ_ASSERT(refCount_ != 0); refCount_--; if (refCount_ == 0) js_free(this); } uint32_t dataLength() const { return dataLength_; } uint8_t* data() { return reinterpret_cast<uint8_t*>(data_); } uint32_t natoms() const { return natoms_; } GCPtrAtom* atoms() { if (!natoms_) return nullptr; return reinterpret_cast<GCPtrAtom*>(data()); } uint32_t codeLength() const { return codeLength_; } jsbytecode* code() { return reinterpret_cast<jsbytecode*>(data() + natoms_ * sizeof(GCPtrAtom)); } void traceChildren(JSTracer* trc); private: SharedScriptData() = delete; SharedScriptData(const SharedScriptData&) = delete; SharedScriptData& operator=(const SharedScriptData&) = delete; }; struct ScriptBytecodeHasher { struct Lookup { const uint8_t* data; uint32_t length; explicit Lookup(SharedScriptData* ssd) : data(ssd->data()), length(ssd->dataLength()) {} }; static HashNumber hash(const Lookup& l) { return mozilla::HashBytes(l.data, l.length); } static bool match(SharedScriptData* entry, const Lookup& lookup) { if (entry->dataLength() != lookup.length) return false; return mozilla::PodEqual<uint8_t>(entry->data(), lookup.data, lookup.length); } }; typedef HashSet<SharedScriptData*, ScriptBytecodeHasher, SystemAllocPolicy> ScriptDataTable; extern void SweepScriptData(JSRuntime* rt, AutoLockForExclusiveAccess& lock); extern void FreeScriptData(JSRuntime* rt, AutoLockForExclusiveAccess& lock); } /* namespace js */ class JSScript : public js::gc::TenuredCell { template <js::XDRMode mode> friend bool js::XDRScript(js::XDRState<mode>* xdr, js::HandleScope enclosingScope, js::HandleScript enclosingScript, js::HandleFunction fun, js::MutableHandleScript scriptp); friend bool js::detail::CopyScript(JSContext* cx, js::HandleScript src, js::HandleScript dst, js::MutableHandle<JS::GCVector<js::Scope*>> scopes); private: js::SharedScriptData* scriptData_; public: uint8_t* data; /* pointer to variable-length data array (see comment above Create() for details) */ JSCompartment* compartment_; private: /* Persistent type information retained across GCs. */ js::TypeScript* types_; // This script's ScriptSourceObject, or a CCW thereof. // // (When we clone a JSScript into a new compartment, we don't clone its // source object. Instead, the clone refers to a wrapper.) js::GCPtrObject sourceObject_; /* * Information attached by Ion. Nexto a valid IonScript this could be * ION_DISABLED_SCRIPT, ION_COMPILING_SCRIPT or ION_PENDING_SCRIPT. * The later is a ion compilation that is ready, but hasn't been linked * yet. */ js::jit::IonScript* ion; /* Information attached by Baseline. */ js::jit::BaselineScript* baseline; /* Information used to re-lazify a lazily-parsed interpreted function. */ js::LazyScript* lazyScript; /* * Pointer to either baseline->method()->raw() or ion->method()->raw(), or * nullptr if there's no Baseline or Ion script. */ uint8_t* baselineOrIonRaw; uint8_t* baselineOrIonSkipArgCheck; // 32-bit fields. uint32_t dataSize_; /* size of the used part of the data array */ uint32_t lineno_; /* base line number of script */ uint32_t column_; /* base column of script, optionally set */ uint32_t mainOffset_;/* offset of main entry point from code, after predef'ing prologue */ uint32_t nfixed_; /* fixed frame slots */ uint32_t nslots_; /* slots plus maximum stack depth */ uint32_t bodyScopeIndex_; /* index into the scopes array of the body scope */ /* Range of characters in scriptSource which contains this script's source. */ uint32_t sourceStart_; uint32_t sourceEnd_; // Number of times the script has been called or has had backedges taken. // When running in ion, also increased for any inlined scripts. Reset if // the script's JIT code is forcibly discarded. mozilla::Atomic<uint32_t, mozilla::Relaxed> warmUpCount; // 16-bit fields. uint16_t warmUpResetCount; /* Number of times the |warmUpCount| was * forcibly discarded. The counter is reset when * a script is successfully jit-compiled. */ uint16_t version; /* JS version under which script was compiled */ uint16_t funLength_; /* ES6 function length */ uint16_t nTypeSets_; /* number of type sets used in this script for dynamic type monitoring */ // Bit fields. public: // The kinds of the optional arrays. enum ArrayKind { CONSTS, OBJECTS, TRYNOTES, SCOPENOTES, ARRAY_KIND_BITS }; private: // The bits in this field indicate the presence/non-presence of several // optional arrays in |data|. See the comments above Create() for details. uint8_t hasArrayBits:ARRAY_KIND_BITS; // The GeneratorKind of the script. uint8_t generatorKindBits_:2; // 1-bit fields. // No need for result value of last expression statement. bool noScriptRval_:1; // Code is in strict mode. bool strict_:1; // Code has "use strict"; explicitly. bool explicitUseStrict_:1; // True if the script has a non-syntactic scope on its dynamic scope chain. // That is, there are objects about which we know nothing between the // outermost syntactic scope and the global. bool hasNonSyntacticScope_:1; // see Parser::selfHostingMode. bool selfHosted_:1; // See FunctionContextFlags. bool bindingsAccessedDynamically_:1; bool funHasExtensibleScope_:1; // True if any formalIsAliased(i). bool funHasAnyAliasedFormal_:1; // Have warned about uses of undefined properties in this script. bool warnedAboutUndefinedProp_:1; // Script has singleton objects. bool hasSingletons_:1; // Script is a lambda to treat as running once or a global or eval script // that will only run once. Which one it is can be disambiguated by // checking whether function() is null. bool treatAsRunOnce_:1; // If treatAsRunOnce, whether script has executed. bool hasRunOnce_:1; // Script has been reused for a clone. bool hasBeenCloned_:1; // Script came from eval(), and is still active. bool isActiveEval_:1; // Script came from eval(), and is in eval cache. bool isCachedEval_:1; // 'this', 'arguments' and f.apply() are used. This is likely to be a wrapper. bool isLikelyConstructorWrapper_:1; // IonMonkey compilation hints. bool failedBoundsCheck_:1; /* script has had hoisted bounds checks fail */ bool failedShapeGuard_:1; /* script has had hoisted shape guard fail */ bool hadFrequentBailouts_:1; bool hadOverflowBailout_:1; bool uninlineable_:1; /* explicitly marked as uninlineable */ // Idempotent cache has triggered invalidation. bool invalidatedIdempotentCache_:1; // Lexical check did fail and bail out. bool failedLexicalCheck_:1; // If the generator was created implicitly via a generator expression, // isGeneratorExp will be true. bool isGeneratorExp_:1; // Script has an entry in JSCompartment::scriptCountsMap. bool hasScriptCounts_:1; // Script has an entry in JSCompartment::debugScriptMap. bool hasDebugScript_:1; // Freeze constraints for stack type sets have been generated. bool hasFreezeConstraints_:1; /* See comments below. */ bool argsHasVarBinding_:1; bool needsArgsAnalysis_:1; bool needsArgsObj_:1; bool functionHasThisBinding_:1; bool functionHasExtraBodyVarScope_:1; // Whether the arguments object for this script, if it needs one, should be // mapped (alias formal parameters). bool hasMappedArgsObj_:1; // Generation for this script's TypeScript. If out of sync with the // TypeZone's generation, the TypeScript needs to be swept. // // This should be a uint32 but is instead a bool so that MSVC packs it // correctly. bool typesGeneration_:1; // Do not relazify this script. This is used by the relazify() testing // function for scripts that are on the stack and also by the AutoDelazify // RAII class. Usually we don't relazify functions in compartments with // scripts on the stack, but the relazify() testing function overrides that, // and sometimes we're working with a cross-compartment function and need to // keep it from relazifying. bool doNotRelazify_:1; // Script contains inner functions. Used to check if we can relazify the // script. bool hasInnerFunctions_:1; bool needsHomeObject_:1; bool isDerivedClassConstructor_:1; bool isDefaultClassConstructor_:1; bool isAsync_:1; // Add padding so JSScript is gc::Cell aligned. Make padding protected // instead of private to suppress -Wunused-private-field compiler warnings. protected: #if JS_BITS_PER_WORD == 32 // Currently no padding is needed. #endif // // End of fields. Start methods. // public: static JSScript* Create(js::ExclusiveContext* cx, const JS::ReadOnlyCompileOptions& options, js::HandleObject sourceObject, uint32_t sourceStart, uint32_t sourceEnd); void initCompartment(js::ExclusiveContext* cx); // Three ways ways to initialize a JSScript. Callers of partiallyInit() // are responsible for notifying the debugger after successfully creating // any kind (function or other) of new JSScript. However, callers of // fullyInitFromEmitter() do not need to do this. static bool partiallyInit(js::ExclusiveContext* cx, JS::Handle<JSScript*> script, uint32_t nscopes, uint32_t nconsts, uint32_t nobjects, uint32_t ntrynotes, uint32_t nscopenotes, uint32_t nyieldoffsets, uint32_t nTypeSets); private: static void initFromFunctionBox(js::ExclusiveContext* cx, js::HandleScript script, js::frontend::FunctionBox* funbox); static void initFromModuleContext(js::ExclusiveContext* cx, js::HandleScript script, js::frontend::ModuleSharedContext* modulesc); public: static bool fullyInitFromEmitter(js::ExclusiveContext* cx, js::HandleScript script, js::frontend::BytecodeEmitter* bce); // Initialize the Function.prototype script. static bool initFunctionPrototype(js::ExclusiveContext* cx, js::HandleScript script, JS::HandleFunction functionProto); #ifdef DEBUG private: // Assert that jump targets are within the code array of the script. void assertValidJumpTargets() const; #endif public: inline JSPrincipals* principals(); JSCompartment* compartment() const { return compartment_; } JSCompartment* maybeCompartment() const { return compartment(); } void setVersion(JSVersion v) { version = v; } js::SharedScriptData* scriptData() { return scriptData_; } // Script bytecode is immutable after creation. jsbytecode* code() const { if (!scriptData_) return nullptr; return scriptData_->code(); } size_t length() const { MOZ_ASSERT(scriptData_); return scriptData_->codeLength(); } jsbytecode* codeEnd() const { return code() + length(); } jsbytecode* lastPC() const { jsbytecode* pc = codeEnd() - js::JSOP_RETRVAL_LENGTH; MOZ_ASSERT(*pc == JSOP_RETRVAL); return pc; } bool containsPC(const jsbytecode* pc) const { return pc >= code() && pc < codeEnd(); } size_t pcToOffset(const jsbytecode* pc) const { MOZ_ASSERT(containsPC(pc)); return size_t(pc - code()); } jsbytecode* offsetToPC(size_t offset) const { MOZ_ASSERT(offset < length()); return code() + offset; } size_t mainOffset() const { return mainOffset_; } size_t lineno() const { return lineno_; } size_t column() const { return column_; } void setColumn(size_t column) { column_ = column; } // The fixed part of a stack frame is comprised of vars (in function and // module code) and block-scoped locals (in all kinds of code). size_t nfixed() const { return nfixed_; } // Number of fixed slots reserved for slots that are always live. Only // nonzero for function or module code. size_t numAlwaysLiveFixedSlots() const { if (bodyScope()->is<js::FunctionScope>()) return bodyScope()->as<js::FunctionScope>().nextFrameSlot(); if (bodyScope()->is<js::ModuleScope>()) return bodyScope()->as<js::ModuleScope>().nextFrameSlot(); return 0; } // Calculate the number of fixed slots that are live at a particular bytecode. size_t calculateLiveFixed(jsbytecode* pc); size_t nslots() const { return nslots_; } unsigned numArgs() const { if (bodyScope()->is<js::FunctionScope>()) return bodyScope()->as<js::FunctionScope>().numPositionalFormalParameters(); return 0; } inline js::Shape* initialEnvironmentShape() const; bool functionHasParameterExprs() const { // Only functions have parameters. js::Scope* scope = bodyScope(); if (!scope->is<js::FunctionScope>()) return false; return scope->as<js::FunctionScope>().hasParameterExprs(); } size_t nTypeSets() const { return nTypeSets_; } size_t funLength() const { return funLength_; } size_t sourceStart() const { return sourceStart_; } size_t sourceEnd() const { return sourceEnd_; } bool noScriptRval() const { return noScriptRval_; } bool strict() const { return strict_; } bool explicitUseStrict() const { return explicitUseStrict_; } bool hasNonSyntacticScope() const { return hasNonSyntacticScope_; } bool selfHosted() const { return selfHosted_; } bool bindingsAccessedDynamically() const { return bindingsAccessedDynamically_; } bool funHasExtensibleScope() const { return funHasExtensibleScope_; } bool funHasAnyAliasedFormal() const { return funHasAnyAliasedFormal_; } bool hasSingletons() const { return hasSingletons_; } bool treatAsRunOnce() const { return treatAsRunOnce_; } bool hasRunOnce() const { return hasRunOnce_; } bool hasBeenCloned() const { return hasBeenCloned_; } void setTreatAsRunOnce() { treatAsRunOnce_ = true; } void setHasRunOnce() { hasRunOnce_ = true; } void setHasBeenCloned() { hasBeenCloned_ = true; } bool isActiveEval() const { return isActiveEval_; } bool isCachedEval() const { return isCachedEval_; } void cacheForEval() { MOZ_ASSERT(isActiveEval() && !isCachedEval()); isActiveEval_ = false; isCachedEval_ = true; // IsEvalCacheCandidate will make sure that there's nothing in this // script that would prevent reexecution even if isRunOnce is // true. So just pretend like we never ran this script. hasRunOnce_ = false; } void uncacheForEval() { MOZ_ASSERT(isCachedEval() && !isActiveEval()); isCachedEval_ = false; isActiveEval_ = true; } void setActiveEval() { isActiveEval_ = true; } bool isLikelyConstructorWrapper() const { return isLikelyConstructorWrapper_; } void setLikelyConstructorWrapper() { isLikelyConstructorWrapper_ = true; } bool isGeneratorExp() const { return isGeneratorExp_; } bool failedBoundsCheck() const { return failedBoundsCheck_; } bool failedShapeGuard() const { return failedShapeGuard_; } bool hadFrequentBailouts() const { return hadFrequentBailouts_; } bool hadOverflowBailout() const { return hadOverflowBailout_; } bool uninlineable() const { return uninlineable_; } bool invalidatedIdempotentCache() const { return invalidatedIdempotentCache_; } bool failedLexicalCheck() const { return failedLexicalCheck_; } bool isDefaultClassConstructor() const { return isDefaultClassConstructor_; } void setFailedBoundsCheck() { failedBoundsCheck_ = true; } void setFailedShapeGuard() { failedShapeGuard_ = true; } void setHadFrequentBailouts() { hadFrequentBailouts_ = true; } void setHadOverflowBailout() { hadOverflowBailout_ = true; } void setUninlineable() { uninlineable_ = true; } void setInvalidatedIdempotentCache() { invalidatedIdempotentCache_ = true; } void setFailedLexicalCheck() { failedLexicalCheck_ = true; } void setIsDefaultClassConstructor() { isDefaultClassConstructor_ = true; } bool hasScriptCounts() const { return hasScriptCounts_; } bool hasFreezeConstraints() const { return hasFreezeConstraints_; } void setHasFreezeConstraints() { hasFreezeConstraints_ = true; } bool warnedAboutUndefinedProp() const { return warnedAboutUndefinedProp_; } void setWarnedAboutUndefinedProp() { warnedAboutUndefinedProp_ = true; } /* See ContextFlags::funArgumentsHasLocalBinding comment. */ bool argumentsHasVarBinding() const { return argsHasVarBinding_; } void setArgumentsHasVarBinding(); bool argumentsAliasesFormals() const { return argumentsHasVarBinding() && hasMappedArgsObj(); } js::GeneratorKind generatorKind() const { return js::GeneratorKindFromBits(generatorKindBits_); } bool isGenerator() const { return generatorKind() != js::NotGenerator; } bool isLegacyGenerator() const { return generatorKind() == js::LegacyGenerator; } bool isStarGenerator() const { return generatorKind() == js::StarGenerator; } void setGeneratorKind(js::GeneratorKind kind) { // A script only gets its generator kind set as part of initialization, // so it can only transition from not being a generator. MOZ_ASSERT(!isGenerator()); generatorKindBits_ = GeneratorKindAsBits(kind); } js::FunctionAsyncKind asyncKind() const { return isAsync_ ? js::AsyncFunction : js::SyncFunction; } void setAsyncKind(js::FunctionAsyncKind kind) { isAsync_ = kind == js::AsyncFunction; } void setNeedsHomeObject() { needsHomeObject_ = true; } bool needsHomeObject() const { return needsHomeObject_; } bool isDerivedClassConstructor() const { return isDerivedClassConstructor_; } /* * As an optimization, even when argsHasLocalBinding, the function prologue * may not need to create an arguments object. This is determined by * needsArgsObj which is set by AnalyzeArgumentsUsage. When !needsArgsObj, * the prologue may simply write MagicValue(JS_OPTIMIZED_ARGUMENTS) to * 'arguments's slot and any uses of 'arguments' will be guaranteed to * handle this magic value. To avoid spurious arguments object creation, we * maintain the invariant that needsArgsObj is only called after the script * has been analyzed. */ bool analyzedArgsUsage() const { return !needsArgsAnalysis_; } inline bool ensureHasAnalyzedArgsUsage(JSContext* cx); bool needsArgsObj() const { MOZ_ASSERT(analyzedArgsUsage()); return needsArgsObj_; } void setNeedsArgsObj(bool needsArgsObj); static bool argumentsOptimizationFailed(JSContext* cx, js::HandleScript script); bool hasMappedArgsObj() const { return hasMappedArgsObj_; } bool functionHasThisBinding() const { return functionHasThisBinding_; } /* * Arguments access (via JSOP_*ARG* opcodes) must access the canonical * location for the argument. If an arguments object exists AND it's mapped * ('arguments' aliases formals), then all access must go through the * arguments object. Otherwise, the local slot is the canonical location for * the arguments. Note: if a formal is aliased through the scope chain, then * script->formalIsAliased and JSOP_*ARG* opcodes won't be emitted at all. */ bool argsObjAliasesFormals() const { return needsArgsObj() && hasMappedArgsObj(); } uint32_t typesGeneration() const { return (uint32_t) typesGeneration_; } void setTypesGeneration(uint32_t generation) { MOZ_ASSERT(generation <= 1); typesGeneration_ = (bool) generation; } void setDoNotRelazify(bool b) { doNotRelazify_ = b; } void setHasInnerFunctions(bool b) { hasInnerFunctions_ = b; } bool hasInnerFunctions() const { return hasInnerFunctions_; } bool hasAnyIonScript() const { return hasIonScript(); } bool hasIonScript() const { bool res = ion && ion != ION_DISABLED_SCRIPT && ion != ION_COMPILING_SCRIPT && ion != ION_PENDING_SCRIPT; MOZ_ASSERT_IF(res, baseline); return res; } bool canIonCompile() const { return ion != ION_DISABLED_SCRIPT; } bool isIonCompilingOffThread() const { return ion == ION_COMPILING_SCRIPT; } js::jit::IonScript* ionScript() const { MOZ_ASSERT(hasIonScript()); return ion; } js::jit::IonScript* maybeIonScript() const { return ion; } js::jit::IonScript* const* addressOfIonScript() const { return &ion; } void setIonScript(JSRuntime* maybeRuntime, js::jit::IonScript* ionScript); bool hasBaselineScript() const { bool res = baseline && baseline != BASELINE_DISABLED_SCRIPT; MOZ_ASSERT_IF(!res, !ion || ion == ION_DISABLED_SCRIPT); return res; } bool canBaselineCompile() const { return baseline != BASELINE_DISABLED_SCRIPT; } js::jit::BaselineScript* baselineScript() const { MOZ_ASSERT(hasBaselineScript()); return baseline; } inline void setBaselineScript(JSRuntime* maybeRuntime, js::jit::BaselineScript* baselineScript); void updateBaselineOrIonRaw(JSRuntime* maybeRuntime); static size_t offsetOfBaselineScript() { return offsetof(JSScript, baseline); } static size_t offsetOfIonScript() { return offsetof(JSScript, ion); } static size_t offsetOfBaselineOrIonRaw() { return offsetof(JSScript, baselineOrIonRaw); } uint8_t* baselineOrIonRawPointer() const { return baselineOrIonRaw; } static size_t offsetOfBaselineOrIonSkipArgCheck() { return offsetof(JSScript, baselineOrIonSkipArgCheck); } bool isRelazifiable() const { return (selfHosted() || lazyScript) && !hasInnerFunctions_ && !types_ && !isGenerator() && !hasBaselineScript() && !hasAnyIonScript() && !doNotRelazify_; } void setLazyScript(js::LazyScript* lazy) { lazyScript = lazy; } js::LazyScript* maybeLazyScript() { return lazyScript; } /* * Original compiled function for the script, if it has a function. * nullptr for global and eval scripts. * The delazifying variant ensures that the function isn't lazy. The * non-delazifying variant must only be used after earlier code has * called ensureNonLazyCanonicalFunction and while the function can't * have been relazified. */ inline JSFunction* functionDelazifying() const; JSFunction* functionNonDelazifying() const { if (bodyScope()->is<js::FunctionScope>()) return bodyScope()->as<js::FunctionScope>().canonicalFunction(); return nullptr; } /* * De-lazifies the canonical function. Must be called before entering code * that expects the function to be non-lazy. */ inline void ensureNonLazyCanonicalFunction(JSContext* cx); js::ModuleObject* module() const { if (bodyScope()->is<js::ModuleScope>()) return bodyScope()->as<js::ModuleScope>().module(); return nullptr; } bool isGlobalOrEvalCode() const { return bodyScope()->is<js::GlobalScope>() || bodyScope()->is<js::EvalScope>(); } bool isGlobalCode() const { return bodyScope()->is<js::GlobalScope>(); } // Returns true if the script may read formal arguments on the stack // directly, via lazy arguments or a rest parameter. bool mayReadFrameArgsDirectly(); JSFlatString* sourceData(JSContext* cx); static bool loadSource(JSContext* cx, js::ScriptSource* ss, bool* worked); void setSourceObject(JSObject* object); JSObject* sourceObject() const { return sourceObject_; } js::ScriptSourceObject& scriptSourceUnwrap() const; js::ScriptSource* scriptSource() const; js::ScriptSource* maybeForwardedScriptSource() const; bool mutedErrors() const { return scriptSource()->mutedErrors(); } const char* filename() const { return scriptSource()->filename(); } const char* maybeForwardedFilename() const { return maybeForwardedScriptSource()->filename(); } public: /* Return whether this script was compiled for 'eval' */ bool isForEval() const { MOZ_ASSERT_IF(isCachedEval() || isActiveEval(), bodyScope()->is<js::EvalScope>()); return isCachedEval() || isActiveEval(); } /* Return whether this is a 'direct eval' script in a function scope. */ bool isDirectEvalInFunction() const { if (!isForEval()) return false; return bodyScope()->hasOnChain(js::ScopeKind::Function); } /* * Return whether this script is a top-level script. * * If we evaluate some code which contains a syntax error, then we might * produce a JSScript which has no associated bytecode. Testing with * |code()| filters out this kind of scripts. * * If this script has a function associated to it, then it is not the * top-level of a file. */ bool isTopLevel() { return code() && !functionNonDelazifying(); } /* Ensure the script has a TypeScript. */ inline bool ensureHasTypes(JSContext* cx); inline js::TypeScript* types(); void maybeSweepTypes(js::AutoClearTypeInferenceStateOnOOM* oom); inline js::GlobalObject& global() const; js::GlobalObject& uninlinedGlobal() const; uint32_t bodyScopeIndex() const { return bodyScopeIndex_; } js::Scope* bodyScope() const { return getScope(bodyScopeIndex_); } js::Scope* outermostScope() const { // The body scope may not be the outermost scope in the script when // the decl env scope is present. size_t index = 0; return getScope(index); } bool functionHasExtraBodyVarScope() const { MOZ_ASSERT_IF(functionHasExtraBodyVarScope_, functionHasParameterExprs()); return functionHasExtraBodyVarScope_; } js::VarScope* functionExtraBodyVarScope() const { MOZ_ASSERT(functionHasExtraBodyVarScope()); for (uint32_t i = 0; i < scopes()->length; i++) { js::Scope* scope = getScope(i); if (scope->kind() == js::ScopeKind::FunctionBodyVar) return &scope->as<js::VarScope>(); } MOZ_CRASH("Function extra body var scope not found"); } inline js::LexicalScope* maybeNamedLambdaScope() const; js::Scope* enclosingScope() const { return outermostScope()->enclosing(); } private: bool makeTypes(JSContext* cx); bool createScriptData(js::ExclusiveContext* cx, uint32_t codeLength, uint32_t srcnotesLength, uint32_t natoms); bool shareScriptData(js::ExclusiveContext* cx); void freeScriptData(); void setScriptData(js::SharedScriptData* data); public: uint32_t getWarmUpCount() const { return warmUpCount; } uint32_t incWarmUpCounter(uint32_t amount = 1) { return warmUpCount += amount; } uint32_t* addressOfWarmUpCounter() { return reinterpret_cast<uint32_t*>(&warmUpCount); } static size_t offsetOfWarmUpCounter() { return offsetof(JSScript, warmUpCount); } void resetWarmUpCounter() { incWarmUpResetCounter(); warmUpCount = 0; } uint16_t getWarmUpResetCount() const { return warmUpResetCount; } uint16_t incWarmUpResetCounter(uint16_t amount = 1) { return warmUpResetCount += amount; } void resetWarmUpResetCounter() { warmUpResetCount = 0; } public: bool initScriptCounts(JSContext* cx); js::ScriptCounts& getScriptCounts(); js::PCCounts* maybeGetPCCounts(jsbytecode* pc); const js::PCCounts* maybeGetThrowCounts(jsbytecode* pc); js::PCCounts* getThrowCounts(jsbytecode* pc); uint64_t getHitCount(jsbytecode* pc); void incHitCount(jsbytecode* pc); // Used when we bailout out of Ion. void addIonCounts(js::jit::IonScriptCounts* ionCounts); js::jit::IonScriptCounts* getIonCounts(); void releaseScriptCounts(js::ScriptCounts* counts); void destroyScriptCounts(js::FreeOp* fop); // The entry should be removed after using this function. void takeOverScriptCountsMapEntry(js::ScriptCounts* entryValue); jsbytecode* main() const { return code() + mainOffset(); } /* * computedSizeOfData() is the in-use size of all the data sections. * sizeOfData() is the size of the block allocated to hold all the data * sections (which can be larger than the in-use size). */ size_t computedSizeOfData() const; size_t sizeOfData(mozilla::MallocSizeOf mallocSizeOf) const; size_t sizeOfTypeScript(mozilla::MallocSizeOf mallocSizeOf) const; uint32_t numNotes(); /* Number of srcnote slots in the srcnotes section */ /* Script notes are allocated right after the code. */ jssrcnote* notes() { return (jssrcnote*)(code() + length()); } bool hasArray(ArrayKind kind) const { return hasArrayBits & (1 << kind); } void setHasArray(ArrayKind kind) { hasArrayBits |= (1 << kind); } void cloneHasArray(JSScript* script) { hasArrayBits = script->hasArrayBits; } bool hasConsts() const { return hasArray(CONSTS); } bool hasObjects() const { return hasArray(OBJECTS); } bool hasTrynotes() const { return hasArray(TRYNOTES); } bool hasScopeNotes() const { return hasArray(SCOPENOTES); } bool hasYieldOffsets() const { return isGenerator(); } #define OFF(fooOff, hasFoo, t) (fooOff() + (hasFoo() ? sizeof(t) : 0)) size_t scopesOffset() const { return 0; } size_t constsOffset() const { return scopesOffset() + sizeof(js::ScopeArray); } size_t objectsOffset() const { return OFF(constsOffset, hasConsts, js::ConstArray); } size_t trynotesOffset() const { return OFF(objectsOffset, hasObjects, js::ObjectArray); } size_t scopeNotesOffset() const { return OFF(trynotesOffset, hasTrynotes, js::TryNoteArray); } size_t yieldOffsetsOffset() const { return OFF(scopeNotesOffset, hasScopeNotes, js::ScopeNoteArray); } #undef OFF size_t dataSize() const { return dataSize_; } js::ConstArray* consts() { MOZ_ASSERT(hasConsts()); return reinterpret_cast<js::ConstArray*>(data + constsOffset()); } js::ObjectArray* objects() { MOZ_ASSERT(hasObjects()); return reinterpret_cast<js::ObjectArray*>(data + objectsOffset()); } js::ScopeArray* scopes() const { return reinterpret_cast<js::ScopeArray*>(data + scopesOffset()); } js::TryNoteArray* trynotes() const { MOZ_ASSERT(hasTrynotes()); return reinterpret_cast<js::TryNoteArray*>(data + trynotesOffset()); } js::ScopeNoteArray* scopeNotes() { MOZ_ASSERT(hasScopeNotes()); return reinterpret_cast<js::ScopeNoteArray*>(data + scopeNotesOffset()); } js::YieldOffsetArray& yieldOffsets() { MOZ_ASSERT(hasYieldOffsets()); return *reinterpret_cast<js::YieldOffsetArray*>(data + yieldOffsetsOffset()); } bool hasLoops(); size_t natoms() const { MOZ_ASSERT(scriptData_); return scriptData_->natoms(); } js::GCPtrAtom* atoms() const { MOZ_ASSERT(scriptData_); return scriptData_->atoms(); } js::GCPtrAtom& getAtom(size_t index) const { MOZ_ASSERT(index < natoms()); return atoms()[index]; } js::GCPtrAtom& getAtom(jsbytecode* pc) const { MOZ_ASSERT(containsPC(pc) && containsPC(pc + sizeof(uint32_t))); return getAtom(GET_UINT32_INDEX(pc)); } js::PropertyName* getName(size_t index) { return getAtom(index)->asPropertyName(); } js::PropertyName* getName(jsbytecode* pc) const { MOZ_ASSERT(containsPC(pc) && containsPC(pc + sizeof(uint32_t))); return getAtom(GET_UINT32_INDEX(pc))->asPropertyName(); } JSObject* getObject(size_t index) { js::ObjectArray* arr = objects(); MOZ_ASSERT(index < arr->length); MOZ_ASSERT(arr->vector[index]->isTenured()); return arr->vector[index]; } JSObject* getObject(jsbytecode* pc) { MOZ_ASSERT(containsPC(pc) && containsPC(pc + sizeof(uint32_t))); return getObject(GET_UINT32_INDEX(pc)); } js::Scope* getScope(size_t index) const { js::ScopeArray* array = scopes(); MOZ_ASSERT(index < array->length); return array->vector[index]; } js::Scope* getScope(jsbytecode* pc) const { // This method is used to get a scope directly using a JSOp with an // index. To search through ScopeNotes to look for a Scope using pc, // use lookupScope. MOZ_ASSERT(containsPC(pc) && containsPC(pc + sizeof(uint32_t))); MOZ_ASSERT(js::JOF_OPTYPE(JSOp(*pc)) == JOF_SCOPE, "Did you mean to use lookupScope(pc)?"); return getScope(GET_UINT32_INDEX(pc)); } JSVersion getVersion() const { return JSVersion(version); } inline JSFunction* getFunction(size_t index); JSFunction* function() const { if (functionNonDelazifying()) return functionNonDelazifying(); return nullptr; } inline js::RegExpObject* getRegExp(size_t index); inline js::RegExpObject* getRegExp(jsbytecode* pc); const js::Value& getConst(size_t index) { js::ConstArray* arr = consts(); MOZ_ASSERT(index < arr->length); return arr->vector[index]; } // The following 3 functions find the static scope just before the // execution of the instruction pointed to by pc. js::Scope* lookupScope(jsbytecode* pc); js::Scope* innermostScope(jsbytecode* pc); js::Scope* innermostScope() { return innermostScope(main()); } /* * The isEmpty method tells whether this script has code that computes any * result (not return value, result AKA normal completion value) other than * JSVAL_VOID, or any other effects. */ bool isEmpty() const { if (length() > 3) return false; jsbytecode* pc = code(); if (noScriptRval() && JSOp(*pc) == JSOP_FALSE) ++pc; return JSOp(*pc) == JSOP_RETRVAL; } bool formalIsAliased(unsigned argSlot); bool formalLivesInArgumentsObject(unsigned argSlot); private: /* Change this->stepMode to |newValue|. */ void setNewStepMode(js::FreeOp* fop, uint32_t newValue); bool ensureHasDebugScript(JSContext* cx); js::DebugScript* debugScript(); js::DebugScript* releaseDebugScript(); void destroyDebugScript(js::FreeOp* fop); public: bool hasBreakpointsAt(jsbytecode* pc); bool hasAnyBreakpointsOrStepMode() { return hasDebugScript_; } // See comment above 'debugMode' in jscompartment.h for explanation of // invariants of debuggee compartments, scripts, and frames. inline bool isDebuggee() const; js::BreakpointSite* getBreakpointSite(jsbytecode* pc) { return hasDebugScript_ ? debugScript()->breakpoints[pcToOffset(pc)] : nullptr; } js::BreakpointSite* getOrCreateBreakpointSite(JSContext* cx, jsbytecode* pc); void destroyBreakpointSite(js::FreeOp* fop, jsbytecode* pc); void clearBreakpointsIn(js::FreeOp* fop, js::Debugger* dbg, JSObject* handler); /* * Increment or decrement the single-step count. If the count is non-zero * then the script is in single-step mode. * * Only incrementing is fallible, as it could allocate a DebugScript. */ bool incrementStepModeCount(JSContext* cx); void decrementStepModeCount(js::FreeOp* fop); bool stepModeEnabled() { return hasDebugScript_ && !!debugScript()->stepMode; } #ifdef DEBUG uint32_t stepModeCount() { return hasDebugScript_ ? debugScript()->stepMode : 0; } #endif void finalize(js::FreeOp* fop); static const JS::TraceKind TraceKind = JS::TraceKind::Script; void traceChildren(JSTracer* trc); // A helper class to prevent relazification of the given function's script // while it's holding on to it. This class automatically roots the script. class AutoDelazify; friend class AutoDelazify; class AutoDelazify { JS::RootedScript script_; JSContext* cx_; bool oldDoNotRelazify_; public: explicit AutoDelazify(JSContext* cx, JS::HandleFunction fun = nullptr) : script_(cx) , cx_(cx) { holdScript(fun); } ~AutoDelazify() { dropScript(); } void operator=(JS::HandleFunction fun) { dropScript(); holdScript(fun); } operator JS::HandleScript() const { return script_; } explicit operator bool() const { return script_; } private: void holdScript(JS::HandleFunction fun); void dropScript(); }; }; /* If this fails, add/remove padding within JSScript. */ static_assert(sizeof(JSScript) % js::gc::CellSize == 0, "Size of JSScript must be an integral multiple of js::gc::CellSize"); namespace js { // Information about a script which may be (or has been) lazily compiled to // bytecode from its source. class LazyScript : public gc::TenuredCell { private: // If non-nullptr, the script has been compiled and this is a forwarding // pointer to the result. This is a weak pointer: after relazification, we // can collect the script if there are no other pointers to it. WeakRef<JSScript*> script_; // Original function with which the lazy script is associated. GCPtrFunction function_; // Scope in which the script is nested. GCPtrScope enclosingScope_; // ScriptSourceObject. We leave this set to nullptr until we generate // bytecode for our immediate parent. This is never a CCW; we don't clone // LazyScripts into other compartments. GCPtrObject sourceObject_; // Heap allocated table with any free variables or inner functions. void* table_; // Add padding so LazyScript is gc::Cell aligned. Make padding protected // instead of private to suppress -Wunused-private-field compiler warnings. protected: #if JS_BITS_PER_WORD == 32 uint32_t padding; #endif private: static const uint32_t NumClosedOverBindingsBits = 21; static const uint32_t NumInnerFunctionsBits = 20; struct PackedView { // Assorted bits that should really be in ScriptSourceObject. uint32_t version : 8; uint32_t shouldDeclareArguments : 1; uint32_t hasThisBinding : 1; uint32_t isAsync : 1; uint32_t numClosedOverBindings : NumClosedOverBindingsBits; uint32_t numInnerFunctions : NumInnerFunctionsBits; uint32_t generatorKindBits : 2; // N.B. These are booleans but need to be uint32_t to pack correctly on MSVC. // If you add another boolean here, make sure to initialze it in // LazyScript::CreateRaw(). uint32_t strict : 1; uint32_t bindingsAccessedDynamically : 1; uint32_t hasDebuggerStatement : 1; uint32_t hasDirectEval : 1; uint32_t isLikelyConstructorWrapper : 1; uint32_t hasBeenCloned : 1; uint32_t treatAsRunOnce : 1; uint32_t isDerivedClassConstructor : 1; uint32_t needsHomeObject : 1; }; union { PackedView p_; uint64_t packedFields_; }; // Source location for the script. uint32_t begin_; uint32_t end_; uint32_t lineno_; uint32_t column_; LazyScript(JSFunction* fun, void* table, uint64_t packedFields, uint32_t begin, uint32_t end, uint32_t lineno, uint32_t column); // Create a LazyScript without initializing the closedOverBindings and the // innerFunctions. To be GC-safe, the caller must initialize both vectors // with valid atoms and functions. static LazyScript* CreateRaw(ExclusiveContext* cx, HandleFunction fun, uint64_t packedData, uint32_t begin, uint32_t end, uint32_t lineno, uint32_t column); public: static const uint32_t NumClosedOverBindingsLimit = 1 << NumClosedOverBindingsBits; static const uint32_t NumInnerFunctionsLimit = 1 << NumInnerFunctionsBits; // Create a LazyScript and initialize closedOverBindings and innerFunctions // with the provided vectors. static LazyScript* Create(ExclusiveContext* cx, HandleFunction fun, const frontend::AtomVector& closedOverBindings, Handle<GCVector<JSFunction*, 8>> innerFunctions, JSVersion version, uint32_t begin, uint32_t end, uint32_t lineno, uint32_t column); // Create a LazyScript and initialize the closedOverBindings and the // innerFunctions with dummy values to be replaced in a later initialization // phase. // // The "script" argument to this function can be null. If it's non-null, // then this LazyScript should be associated with the given JSScript. // // The enclosingScript and enclosingScope arguments may be null if the // enclosing function is also lazy. static LazyScript* Create(ExclusiveContext* cx, HandleFunction fun, HandleScript script, HandleScope enclosingScope, HandleScript enclosingScript, uint64_t packedData, uint32_t begin, uint32_t end, uint32_t lineno, uint32_t column); void initRuntimeFields(uint64_t packedFields); inline JSFunction* functionDelazifying(JSContext* cx) const; JSFunction* functionNonDelazifying() const { return function_; } void initScript(JSScript* script); void resetScript(); JSScript* maybeScript() { return script_; } const JSScript* maybeScriptUnbarriered() const { return script_.unbarrieredGet(); } bool hasScript() const { return bool(script_); } Scope* enclosingScope() const { return enclosingScope_; } ScriptSourceObject* sourceObject() const; ScriptSource* scriptSource() const { return sourceObject()->source(); } ScriptSource* maybeForwardedScriptSource() const; bool mutedErrors() const { return scriptSource()->mutedErrors(); } JSVersion version() const { JS_STATIC_ASSERT(JSVERSION_UNKNOWN == -1); return (p_.version == JS_BIT(8) - 1) ? JSVERSION_UNKNOWN : JSVersion(p_.version); } void setEnclosingScopeAndSource(Scope* enclosingScope, ScriptSourceObject* sourceObject); uint32_t numClosedOverBindings() const { return p_.numClosedOverBindings; } JSAtom** closedOverBindings() { return (JSAtom**)table_; } uint32_t numInnerFunctions() const { return p_.numInnerFunctions; } GCPtrFunction* innerFunctions() { return (GCPtrFunction*)&closedOverBindings()[numClosedOverBindings()]; } GeneratorKind generatorKind() const { return GeneratorKindFromBits(p_.generatorKindBits); } bool isGenerator() const { return generatorKind() != NotGenerator; } bool isLegacyGenerator() const { return generatorKind() == LegacyGenerator; } bool isStarGenerator() const { return generatorKind() == StarGenerator; } void setGeneratorKind(GeneratorKind kind) { // A script only gets its generator kind set as part of initialization, // so it can only transition from NotGenerator. MOZ_ASSERT(!isGenerator()); // Legacy generators cannot currently be lazy. MOZ_ASSERT(kind != LegacyGenerator); p_.generatorKindBits = GeneratorKindAsBits(kind); } FunctionAsyncKind asyncKind() const { return p_.isAsync ? AsyncFunction : SyncFunction; } void setAsyncKind(FunctionAsyncKind kind) { p_.isAsync = kind == AsyncFunction; } bool strict() const { return p_.strict; } void setStrict() { p_.strict = true; } bool bindingsAccessedDynamically() const { return p_.bindingsAccessedDynamically; } void setBindingsAccessedDynamically() { p_.bindingsAccessedDynamically = true; } bool hasDebuggerStatement() const { return p_.hasDebuggerStatement; } void setHasDebuggerStatement() { p_.hasDebuggerStatement = true; } bool hasDirectEval() const { return p_.hasDirectEval; } void setHasDirectEval() { p_.hasDirectEval = true; } bool isLikelyConstructorWrapper() const { return p_.isLikelyConstructorWrapper; } void setLikelyConstructorWrapper() { p_.isLikelyConstructorWrapper = true; } bool hasBeenCloned() const { return p_.hasBeenCloned; } void setHasBeenCloned() { p_.hasBeenCloned = true; } bool treatAsRunOnce() const { return p_.treatAsRunOnce; } void setTreatAsRunOnce() { p_.treatAsRunOnce = true; } bool isDerivedClassConstructor() const { return p_.isDerivedClassConstructor; } void setIsDerivedClassConstructor() { p_.isDerivedClassConstructor = true; } bool needsHomeObject() const { return p_.needsHomeObject; } void setNeedsHomeObject() { p_.needsHomeObject = true; } bool shouldDeclareArguments() const { return p_.shouldDeclareArguments; } void setShouldDeclareArguments() { p_.shouldDeclareArguments = true; } bool hasThisBinding() const { return p_.hasThisBinding; } void setHasThisBinding() { p_.hasThisBinding = true; } const char* filename() const { return scriptSource()->filename(); } uint32_t begin() const { return begin_; } uint32_t end() const { return end_; } uint32_t lineno() const { return lineno_; } uint32_t column() const { return column_; } bool hasUncompiledEnclosingScript() const; friend class GCMarker; void traceChildren(JSTracer* trc); void finalize(js::FreeOp* fop); static const JS::TraceKind TraceKind = JS::TraceKind::LazyScript; size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) { return mallocSizeOf(table_); } uint64_t packedFields() const { return packedFields_; } }; /* If this fails, add/remove padding within LazyScript. */ JS_STATIC_ASSERT(sizeof(LazyScript) % js::gc::CellSize == 0); struct ScriptAndCounts { /* This structure is stored and marked from the JSRuntime. */ JSScript* script; ScriptCounts scriptCounts; inline explicit ScriptAndCounts(JSScript* script); inline ScriptAndCounts(ScriptAndCounts&& sac); const PCCounts* maybeGetPCCounts(jsbytecode* pc) const { return scriptCounts.maybeGetPCCounts(script->pcToOffset(pc)); } const PCCounts* maybeGetThrowCounts(jsbytecode* pc) const { return scriptCounts.maybeGetThrowCounts(script->pcToOffset(pc)); } jit::IonScriptCounts* getIonCounts() const { return scriptCounts.ionCounts_; } void trace(JSTracer* trc) { TraceRoot(trc, &script, "ScriptAndCounts::script"); } }; struct GSNCache; jssrcnote* GetSrcNote(GSNCache& cache, JSScript* script, jsbytecode* pc); extern jssrcnote* GetSrcNote(JSContext* cx, JSScript* script, jsbytecode* pc); extern jsbytecode* LineNumberToPC(JSScript* script, unsigned lineno); extern JS_FRIEND_API(unsigned) GetScriptLineExtent(JSScript* script); } /* namespace js */ namespace js { extern unsigned PCToLineNumber(JSScript* script, jsbytecode* pc, unsigned* columnp = nullptr); extern unsigned PCToLineNumber(unsigned startLine, jssrcnote* notes, jsbytecode* code, jsbytecode* pc, unsigned* columnp = nullptr); /* * This function returns the file and line number of the script currently * executing on cx. If there is no current script executing on cx (e.g., a * native called directly through JSAPI (e.g., by setTimeout)), nullptr and 0 * are returned as the file and line. Additionally, this function avoids the * full linear scan to compute line number when the caller guarantees that the * script compilation occurs at a JSOP_EVAL/JSOP_SPREADEVAL. */ enum LineOption { CALLED_FROM_JSOP_EVAL, NOT_CALLED_FROM_JSOP_EVAL }; extern void DescribeScriptedCallerForCompilation(JSContext* cx, MutableHandleScript maybeScript, const char** file, unsigned* linenop, uint32_t* pcOffset, bool* mutedErrors, LineOption opt = NOT_CALLED_FROM_JSOP_EVAL); JSScript* CloneScriptIntoFunction(JSContext* cx, HandleScope enclosingScope, HandleFunction fun, HandleScript src); JSScript* CloneGlobalScript(JSContext* cx, ScopeKind scopeKind, HandleScript src); } /* namespace js */ // JS::ubi::Nodes can point to js::LazyScripts; they're js::gc::Cell instances // with no associated compartment. namespace JS { namespace ubi { template<> class Concrete<js::LazyScript> : TracerConcrete<js::LazyScript> { protected: explicit Concrete(js::LazyScript *ptr) : TracerConcrete<js::LazyScript>(ptr) { } public: static void construct(void *storage, js::LazyScript *ptr) { new (storage) Concrete(ptr); } CoarseType coarseType() const final { return CoarseType::Script; } Size size(mozilla::MallocSizeOf mallocSizeOf) const override; const char* scriptFilename() const final; const char16_t* typeName() const override { return concreteTypeName; } static const char16_t concreteTypeName[]; }; } // namespace ubi } // namespace JS #endif /* jsscript_h */