diff options
Diffstat (limited to 'js/src/jsscript.h')
-rw-r--r-- | js/src/jsscript.h | 2276 |
1 files changed, 2276 insertions, 0 deletions
diff --git a/js/src/jsscript.h b/js/src/jsscript.h new file mode 100644 index 000000000..bc8bda83d --- /dev/null +++ b/js/src/jsscript.h @@ -0,0 +1,2276 @@ +/* -*- 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 */ |