/* -*- 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/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 jit { struct BaselineScript; struct IonScriptCounts; } # define ION_DISABLED_SCRIPT ((js::jit::IonScript*)0x1) # define ION_COMPILING_SCRIPT ((js::jit::IonScript*)0x2) # define BASELINE_DISABLED_SCRIPT ((js::jit::BaselineScript*)0x1) class BreakpointSite; class BindingIter; class LazyScript; class RegExpObject; struct SourceCompressionTask; class Shape; class WatchpointMap; class NestedScopeObject; namespace frontend { struct BytecodeEmitter; class UpvarCookie; } } /* * 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::HeapPtrNativeObject* 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 { 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 { // 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); } }; JS_STATIC_ASSERT(sizeof(Binding) == sizeof(uintptr_t)); class Bindings; typedef InternalHandle InternalBindingsHandle; /* * 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 { friend class BindingIter; friend class AliasedFormalIter; 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); } inline Bindings(); /* * 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, InternalBindingsHandle self, uint32_t numArgs, uint32_t numVars, uint32_t numBodyLevelLexicals, uint32_t numBlockScoped, uint32_t numUnaliasedVars, uint32_t numUnaliasedBodyLevelLexicals, Binding* bindingArray); // 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, InternalBindingsHandle 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'. */ static BindingIter argumentsBinding(ExclusiveContext* cx, InternalBindingsHandle); /* 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(); } static js::ThingRootKind rootKind() { return js::THING_ROOT_BINDINGS; } void trace(JSTracer* trc); }; template <> struct GCMethods { static Bindings initial(); static bool poisoned(const Bindings& bindings) { return IsPoisonedPtr(bindings.callObjShape()); } }; class ScriptCounts { friend class ::JSScript; friend struct ScriptAndCounts; /* * This points to a single block that holds an array of PCCounts followed * by an array of doubles. Each element in the PCCounts array has a * pointer into the array of doubles. */ PCCounts* pcCountsVector; /* Information about any Ion compilations for the script. */ jit::IonScriptCounts* ionCounts; public: ScriptCounts() : pcCountsVector(nullptr), ionCounts(nullptr) { } inline void destroy(FreeOp* fop); void set(js::ScriptCounts counts) { pcCountsVector = counts.pcCountsVector; ionCounts = counts.ionCounts; } }; 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