/* -*- 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/UniquePtr.h" #include "jsatom.h" #include "jslock.h" #include "jsopcode.h" #include "jstypes.h" #include "gc/Barrier.h" #include "gc/Rooting.h" #include "jit/IonCode.h" #include "js/UbiNode.h" #include "vm/NativeObject.h" #include "vm/Shape.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 BindingIter; class Debugger; class LazyScript; class ModuleObject; class NestedScopeObject; class RegExpObject; struct SourceCompressionTask; class Shape; namespace frontend { struct BytecodeEmitter; class UpvarCookie; class FunctionBox; class ModuleBox; } // namespace frontend namespace detail { // Do not call this directly! It is exposed for the friend declarations in // this file. bool CopyScript(JSContext* cx, HandleObject scriptStaticScope, HandleScript src, HandleScript dst); } // 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 // BlockScopeNote 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 NoBlockScopeIndex. // struct BlockScopeNote { static const uint32_t NoBlockScopeIndex = UINT32_MAX; uint32_t index; // Index of NestedScopeObject in the object // array, or NoBlockScopeIndex 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 UINT32_MAX. }; struct ConstArray { js::HeapValue* vector; /* array of indexed constant values */ uint32_t length; }; struct ObjectArray { js::HeapPtrObject* vector; // Array of indexed objects. uint32_t length; // Count of indexed objects. }; struct TryNoteArray { JSTryNote* vector; // Array of indexed try notes. uint32_t length; // Count of indexed try notes. }; struct BlockScopeArray { BlockScopeNote* vector; // Array of indexed BlockScopeNote records. uint32_t length; // Count of indexed try notes. }; class YieldOffsetArray { friend bool detail::CopyScript(JSContext* cx, HandleObject scriptStaticScope, HandleScript src, HandleScript dst); 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 Binding : public JS::Traceable { // One JSScript stores one Binding per formal/variable so we use a // packed-word representation. uintptr_t bits_; static const uintptr_t KIND_MASK = 0x3; static const uintptr_t ALIASED_BIT = 0x4; static const uintptr_t NAME_MASK = ~(KIND_MASK | ALIASED_BIT); public: // A "binding" is a formal parameter, 'var' (also a stand in for // body-level 'let' declarations), or 'const' declaration. A function's // lexical scope is composed of these three kinds of bindings. enum Kind { ARGUMENT, VARIABLE, CONSTANT }; explicit Binding() : bits_(0) {} Binding(PropertyName* name, Kind kind, bool aliased) { JS_STATIC_ASSERT(CONSTANT <= KIND_MASK); MOZ_ASSERT((uintptr_t(name) & ~NAME_MASK) == 0); MOZ_ASSERT((uintptr_t(kind) & ~KIND_MASK) == 0); bits_ = uintptr_t(name) | uintptr_t(kind) | (aliased ? ALIASED_BIT : 0); } PropertyName* name() const { return (PropertyName*)(bits_ & NAME_MASK); } Kind kind() const { return Kind(bits_ & KIND_MASK); } bool aliased() const { return bool(bits_ & ALIASED_BIT); } static void trace(Binding* self, JSTracer* trc) { self->trace(trc); } void trace(JSTracer* trc); }; JS_STATIC_ASSERT(sizeof(Binding) == sizeof(uintptr_t)); /* * Formal parameters and local variables are stored in a shape tree * path encapsulated within this class. This class represents bindings for * both function and top-level scripts (the latter is needed to track names in * strict mode eval code, to give such code its own lexical environment). */ class Bindings : public JS::Traceable { friend class BindingIter; friend class AliasedFormalIter; template friend class BindingsOperations; template friend class MutableBindingsOperations; RelocatablePtrShape callObjShape_; uintptr_t bindingArrayAndFlag_; uint16_t numArgs_; uint16_t numBlockScoped_; uint16_t numBodyLevelLexicals_; uint16_t numUnaliasedBodyLevelLexicals_; uint32_t aliasedBodyLevelLexicalBegin_; uint32_t numVars_; uint32_t numUnaliasedVars_; #if JS_BITS_PER_WORD == 32 // Bindings is allocated inline inside JSScript, which needs to be // gc::Cell aligned. uint32_t padding_; #endif /* * During parsing, bindings are allocated out of a temporary LifoAlloc. * After parsing, a JSScript object is created and the bindings are * permanently transferred to it. On error paths, the JSScript object may * end up with bindings that still point to the (new released) LifoAlloc * memory. To avoid tracing these bindings during GC, we keep track of * whether the bindings are temporary or permanent in the low bit of * bindingArrayAndFlag_. */ static const uintptr_t TEMPORARY_STORAGE_BIT = 0x1; bool bindingArrayUsingTemporaryStorage() const { return bindingArrayAndFlag_ & TEMPORARY_STORAGE_BIT; } public: static const uint32_t BLOCK_SCOPED_LIMIT = UINT16_LIMIT; Binding* bindingArray() const { return reinterpret_cast(bindingArrayAndFlag_ & ~TEMPORARY_STORAGE_BIT); } Bindings() : callObjShape_(nullptr), bindingArrayAndFlag_(TEMPORARY_STORAGE_BIT), numArgs_(0), numBlockScoped_(0), numBodyLevelLexicals_(0), numUnaliasedBodyLevelLexicals_(0), numVars_(0), numUnaliasedVars_(0) {} /* * Initialize a Bindings with a pointer into temporary storage. * bindingArray must have length numArgs + numVars + * numBodyLevelLexicals. Before the temporary storage is release, * switchToScriptStorage must be called, providing a pointer into the * Binding array stored in script->data. */ static bool initWithTemporaryStorage(ExclusiveContext* cx, MutableHandle self, uint32_t numArgs, uint32_t numVars, uint32_t numBodyLevelLexicals, uint32_t numBlockScoped, uint32_t numUnaliasedVars, uint32_t numUnaliasedBodyLevelLexicals, const Binding* bindingArray, bool isModule = false); // Initialize a trivial Bindings with no slots and an empty callObjShape. bool initTrivial(ExclusiveContext* cx); // CompileScript parses and compiles one statement at a time, but the result // is one Script object. There will be no vars or bindings, because those // go on the global, but there may be block-scoped locals, and the number of // block-scoped locals may increase as we parse more expressions. This // helper updates the number of block scoped variables in a script as it is // being parsed. void updateNumBlockScoped(unsigned numBlockScoped) { MOZ_ASSERT(!callObjShape_); MOZ_ASSERT(numVars_ == 0); MOZ_ASSERT(numBlockScoped < LOCALNO_LIMIT); MOZ_ASSERT(numBlockScoped >= numBlockScoped_); numBlockScoped_ = numBlockScoped; } void setAllLocalsAliased() { numBlockScoped_ = 0; } uint8_t* switchToScriptStorage(Binding* newStorage); /* * Clone srcScript's bindings (as part of js::CloneScript). dstScriptData * is the pointer to what will eventually be dstScript->data. */ static bool clone(JSContext* cx, MutableHandle self, uint8_t* dstScriptData, HandleScript srcScript); uint32_t numArgs() const { return numArgs_; } uint32_t numVars() const { return numVars_; } uint32_t numBodyLevelLexicals() const { return numBodyLevelLexicals_; } uint32_t numBlockScoped() const { return numBlockScoped_; } uint32_t numBodyLevelLocals() const { return numVars_ + numBodyLevelLexicals_; } uint32_t numUnaliasedBodyLevelLocals() const { return numUnaliasedVars_ + numUnaliasedBodyLevelLexicals_; } uint32_t numAliasedBodyLevelLocals() const { return numBodyLevelLocals() - numUnaliasedBodyLevelLocals(); } uint32_t numLocals() const { return numVars() + numBodyLevelLexicals() + numBlockScoped(); } uint32_t numFixedLocals() const { return numUnaliasedVars() + numUnaliasedBodyLevelLexicals() + numBlockScoped(); } uint32_t lexicalBegin() const { return numArgs() + numVars(); } uint32_t aliasedBodyLevelLexicalBegin() const { return aliasedBodyLevelLexicalBegin_; } uint32_t numUnaliasedVars() const { return numUnaliasedVars_; } uint32_t numUnaliasedBodyLevelLexicals() const { return numUnaliasedBodyLevelLexicals_; } // Return the size of the bindingArray. uint32_t count() const { return numArgs() + numVars() + numBodyLevelLexicals(); } /* Return the initial shape of call objects created for this scope. */ Shape* callObjShape() const { return callObjShape_; } /* Convenience method to get the var index of 'arguments' or 'this'. */ static BindingIter argumentsBinding(ExclusiveContext* cx, HandleScript script); static BindingIter thisBinding(ExclusiveContext* cx, HandleScript script); /* Return whether the binding at bindingIndex is aliased. */ bool bindingIsAliased(uint32_t bindingIndex); /* Return whether this scope has any aliased bindings. */ bool hasAnyAliasedBindings() const { if (!callObjShape_) return false; return !callObjShape_->isEmptyShape(); } Binding* begin() const { return bindingArray(); } Binding* end() const { return bindingArray() + count(); } static void trace(Bindings* self, JSTracer* trc) { self->trace(trc); } void trace(JSTracer* trc); }; // If this fails, add/remove padding within Bindings. static_assert(sizeof(Bindings) % js::gc::CellSize == 0, "Size of Bindings must be an integral multiple of js::gc::CellSize"); template class BindingsOperations { const Bindings& bindings() const { return static_cast(this)->get(); } public: // Direct data access to the underlying bindings. const RelocatablePtrShape& callObjShape() const { return bindings().callObjShape_; } uint16_t numArgs() const { return bindings().numArgs_; } uint16_t numBlockScoped() const { return bindings().numBlockScoped_; } uint16_t numBodyLevelLexicals() const { return bindings().numBodyLevelLexicals_; } uint16_t aliasedBodyLevelLexicalBegin() const { return bindings().aliasedBodyLevelLexicalBegin_; } uint16_t numUnaliasedBodyLevelLexicals() const { return bindings().numUnaliasedBodyLevelLexicals_; } uint32_t numVars() const { return bindings().numVars_; } uint32_t numUnaliasedVars() const { return bindings().numUnaliasedVars_; } // Binding array access. bool bindingArrayUsingTemporaryStorage() const { return bindings().bindingArrayUsingTemporaryStorage(); } const Binding* bindingArray() const { return bindings().bindingArray(); } uint32_t count() const { return bindings().count(); } // Helpers. uint32_t numBodyLevelLocals() const { return numVars() + numBodyLevelLexicals(); } uint32_t numUnaliasedBodyLevelLocals() const { return numUnaliasedVars() + numUnaliasedBodyLevelLexicals(); } uint32_t numAliasedBodyLevelLocals() const { return numBodyLevelLocals() - numUnaliasedBodyLevelLocals(); } uint32_t numLocals() const { return numVars() + numBodyLevelLexicals() + numBlockScoped(); } uint32_t numFixedLocals() const { return numUnaliasedVars() + numUnaliasedBodyLevelLexicals() + numBlockScoped(); } uint32_t lexicalBegin() const { return numArgs() + numVars(); } }; template class MutableBindingsOperations : public BindingsOperations { Bindings& bindings() { return static_cast(this)->get(); } public: void setCallObjShape(HandleShape shape) { bindings().callObjShape_ = shape; } void setBindingArray(const Binding* bindingArray, uintptr_t temporaryBit) { bindings().bindingArrayAndFlag_ = uintptr_t(bindingArray) | temporaryBit; } void setNumArgs(uint16_t num) { bindings().numArgs_ = num; } void setNumVars(uint32_t num) { bindings().numVars_ = num; } void setNumBodyLevelLexicals(uint16_t num) { bindings().numBodyLevelLexicals_ = num; } void setNumBlockScoped(uint16_t num) { bindings().numBlockScoped_ = num; } void setNumUnaliasedVars(uint32_t num) { bindings().numUnaliasedVars_ = num; } void setNumUnaliasedBodyLevelLexicals(uint16_t num) { bindings().numUnaliasedBodyLevelLexicals_ = num; } void setAliasedBodyLevelLexicalBegin(uint32_t offset) { bindings().aliasedBodyLevelLexicalBegin_ = offset; } uint8_t* switchToScriptStorage(Binding* permanentStorage) { return bindings().switchToScriptStorage(permanentStorage); } }; template <> class HandleBase : public BindingsOperations> {}; template <> class MutableHandleBase : public MutableBindingsOperations> {}; 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; /* * 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; class UncompressedSourceCache { typedef HashMap, SystemAllocPolicy> Map; public: // Hold an entry in the source data cache and prevent it from being purged on GC. class AutoHoldEntry { UncompressedSourceCache* cache_; ScriptSource* source_; const char16_t* charsToFree_; public: explicit AutoHoldEntry(); ~AutoHoldEntry(); private: void holdEntry(UncompressedSourceCache* cache, ScriptSource* source); void deferDelete(const char16_t* chars); ScriptSource* source() const { return source_; } friend class UncompressedSourceCache; }; private: Map* map_; AutoHoldEntry* holder_; public: UncompressedSourceCache() : map_(nullptr), holder_(nullptr) {} const char16_t* lookup(ScriptSource* ss, AutoHoldEntry& asp); bool put(ScriptSource* ss, const char16_t* chars, AutoHoldEntry& asp); void purge(); size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf); private: void holdEntry(AutoHoldEntry& holder, ScriptSource* ss); 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. enum { DataMissing, DataUncompressed, DataCompressed, DataParent } dataType; union { struct { const char16_t* chars; bool ownsChars; } uncompressed; struct { void* raw; size_t nbytes; HashNumber hash; } compressed; ScriptSource* parent; } data; uint32_t length_; // The filename of this script. mozilla::UniquePtr filename_; mozilla::UniquePtr displayURL_; mozilla::UniquePtr 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. mozilla::UniquePtr 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