/* -*- 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/Maybe.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> 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, JSTRY_FOR_OF_ITERCLOSE, JSTRY_DESTRUCTURING_ITERCLOSE }; /* * 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> 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 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, 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, 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::hash(ssc.ss), ssc.chunk); } static bool match(const ScriptSourceChunk& c1, const ScriptSourceChunk& c2) { return c1 == c2; } }; class UncompressedSourceCache { typedef HashMap 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_; 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; 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 source is for Function constructor, the position of ")" after // parameter list in the source. This is used to get function body. // 0 for other cases. uint32_t parameterListEnd_; // 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