diff options
Diffstat (limited to '')
-rw-r--r-- | js/src/vm/JSContext.h | 1281 |
1 files changed, 1281 insertions, 0 deletions
diff --git a/js/src/vm/JSContext.h b/js/src/vm/JSContext.h new file mode 100644 index 0000000000..67eece0a25 --- /dev/null +++ b/js/src/vm/JSContext.h @@ -0,0 +1,1281 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * 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 execution context. */ + +#ifndef vm_JSContext_h +#define vm_JSContext_h + +#include "mozilla/MemoryReporting.h" + +#include "jstypes.h" // JS_PUBLIC_API + +#include "ds/TraceableFifo.h" +#include "gc/Memory.h" +#include "irregexp/RegExpTypes.h" +#include "js/CharacterEncoding.h" +#include "js/ContextOptions.h" // JS::ContextOptions +#include "js/GCVector.h" +#include "js/Promise.h" +#include "js/Result.h" +#include "js/Utility.h" +#include "js/Vector.h" +#include "threading/ProtectedData.h" +#include "util/StructuredSpewer.h" +#include "vm/Activation.h" // js::Activation +#include "vm/ErrorReporting.h" +#include "vm/MallocProvider.h" +#include "vm/Runtime.h" +#include "vm/SharedStencil.h" // js::SharedImmutableScriptDataTable +#include "wasm/WasmContext.h" + +struct JS_PUBLIC_API JSContext; + +struct DtoaState; + +namespace js { + +class AutoAllocInAtomsZone; +class AutoMaybeLeaveAtomsZone; +class AutoRealm; + +namespace frontend { +class WellKnownParserAtoms; +} // namespace frontend + +namespace jit { +class ICScript; +class JitActivation; +class JitContext; +class DebugModeOSRVolatileJitFrameIter; +} // namespace jit + +namespace gc { +class AutoCheckCanAccessAtomsDuringGC; +class AutoSuppressNurseryCellAlloc; +} // namespace gc + +/* Detects cycles when traversing an object graph. */ +class MOZ_RAII AutoCycleDetector { + public: + using Vector = GCVector<JSObject*, 8>; + + AutoCycleDetector(JSContext* cx, HandleObject objArg) + : cx(cx), obj(cx, objArg), cyclic(true) {} + + ~AutoCycleDetector(); + + bool init(); + + bool foundCycle() { return cyclic; } + + private: + JSContext* cx; + RootedObject obj; + bool cyclic; +}; + +struct AutoResolving; + +struct ParseTask; + +class InternalJobQueue : public JS::JobQueue { + public: + explicit InternalJobQueue(JSContext* cx) + : queue(cx, SystemAllocPolicy()), draining_(false), interrupted_(false) {} + ~InternalJobQueue() = default; + + // JS::JobQueue methods. + JSObject* getIncumbentGlobal(JSContext* cx) override; + bool enqueuePromiseJob(JSContext* cx, JS::HandleObject promise, + JS::HandleObject job, JS::HandleObject allocationSite, + JS::HandleObject incumbentGlobal) override; + void runJobs(JSContext* cx) override; + bool empty() const override; + + // If we are currently in a call to runJobs(), make that call stop processing + // jobs once the current one finishes, and return. If we are not currently in + // a call to runJobs, make all future calls return immediately. + void interrupt() { interrupted_ = true; } + + // Return the front element of the queue, or nullptr if the queue is empty. + // This is only used by shell testing functions. + JSObject* maybeFront() const; + + private: + using Queue = js::TraceableFifo<JSObject*, 0, SystemAllocPolicy>; + + JS::PersistentRooted<Queue> queue; + + // True if we are in the midst of draining jobs from this queue. We use this + // to avoid re-entry (nested calls simply return immediately). + bool draining_; + + // True if we've been asked to interrupt draining jobs. Set by interrupt(). + bool interrupted_; + + class SavedQueue; + js::UniquePtr<JobQueue::SavedJobQueue> saveJobQueue(JSContext*) override; +}; + +class AutoLockScriptData; + +void ReportOverRecursed(JSContext* cx, unsigned errorNumber); + +/* Thread Local Storage slot for storing the context for a thread. */ +extern MOZ_THREAD_LOCAL(JSContext*) TlsContext; + +enum class ContextKind { + Uninitialized, + + // Context for the main thread of a JSRuntime. + MainThread, + + // Context for a helper thread. + HelperThread +}; + +#ifdef DEBUG +JSContext* MaybeGetJSContext(); +bool CurrentThreadIsParseThread(); +#endif + +enum class InterruptReason : uint32_t { + GC = 1 << 0, + AttachIonCompilations = 1 << 1, + CallbackUrgent = 1 << 2, + CallbackCanWait = 1 << 3, +}; + +} /* namespace js */ + +/* + * A JSContext encapsulates the thread local state used when using the JS + * runtime. + */ +struct JS_PUBLIC_API JSContext : public JS::RootingContext, + public js::MallocProvider<JSContext> { + JSContext(JSRuntime* runtime, const JS::ContextOptions& options); + ~JSContext(); + + bool init(js::ContextKind kind); + + private: + js::UnprotectedData<JSRuntime*> runtime_; + js::WriteOnceData<js::ContextKind> kind_; + + friend class js::gc::AutoSuppressNurseryCellAlloc; + js::ContextData<size_t> nurserySuppressions_; + + js::ContextData<JS::ContextOptions> options_; + + // Free lists for allocating in the current zone. + js::ContextData<js::gc::FreeLists*> freeLists_; + + // This is reset each time we switch zone, then added to the variable in the + // zone when we switch away from it. This would be a js::ThreadData but we + // need to take its address. + uint32_t allocsThisZoneSinceMinorGC_; + + // Free lists for parallel allocation in the atoms zone on helper threads. + js::ContextData<js::gc::FreeLists*> atomsZoneFreeLists_; + + js::ContextData<JSFreeOp> defaultFreeOp_; + + // Thread that the JSContext is currently running on, if in use. + js::ThreadId currentThread_; + + js::ParseTask* parseTask_; + + // When a helper thread is using a context, it may need to periodically + // free unused memory. + mozilla::Atomic<bool, mozilla::ReleaseAcquire> freeUnusedMemory; + + // Are we currently timing execution? This flag ensures that we do not + // double-count execution time in reentrant situations. + js::ContextData<bool> measuringExecutionTime_; + + public: + // This is used by helper threads to change the runtime their context is + // currently operating on. + void setRuntime(JSRuntime* rt); + + void setHelperThread(const js::AutoLockHelperThreadState& locked); + void clearHelperThread(const js::AutoLockHelperThreadState& locked); + + bool contextAvailable(js::AutoLockHelperThreadState& locked) { + MOZ_ASSERT(kind_ == js::ContextKind::HelperThread); + return currentThread_ == js::ThreadId(); + } + + void setFreeUnusedMemory(bool shouldFree) { freeUnusedMemory = shouldFree; } + + bool shouldFreeUnusedMemory() const { + return kind_ == js::ContextKind::HelperThread && freeUnusedMemory; + } + + bool isMeasuringExecutionTime() const { return measuringExecutionTime_; } + void setIsMeasuringExecutionTime(bool value) { + measuringExecutionTime_ = value; + } + +#ifdef DEBUG + bool isInitialized() const { return kind_ != js::ContextKind::Uninitialized; } +#endif + + bool isMainThreadContext() const { + return kind_ == js::ContextKind::MainThread; + } + + bool isHelperThreadContext() const { + return kind_ == js::ContextKind::HelperThread; + } + + js::gc::FreeLists& freeLists() { + MOZ_ASSERT(freeLists_); + return *freeLists_; + } + + js::gc::FreeLists& atomsZoneFreeLists() { + MOZ_ASSERT(atomsZoneFreeLists_); + return *atomsZoneFreeLists_; + } + + template <typename T> + bool isInsideCurrentZone(T thing) const { + return thing->zoneFromAnyThread() == zone_; + } + + template <typename T> + inline bool isInsideCurrentCompartment(T thing) const { + return thing->compartment() == compartment(); + } + + void* onOutOfMemory(js::AllocFunction allocFunc, arena_id_t arena, + size_t nbytes, void* reallocPtr = nullptr) { + if (isHelperThreadContext()) { + addPendingOutOfMemory(); + return nullptr; + } + return runtime_->onOutOfMemory(allocFunc, arena, nbytes, reallocPtr, this); + } + + /* Clear the pending exception (if any) due to OOM. */ + void recoverFromOutOfMemory(); + + void reportAllocationOverflow() { js::ReportAllocationOverflow(this); } + + void noteTenuredAlloc() { allocsThisZoneSinceMinorGC_++; } + + uint32_t* addressOfTenuredAllocCount() { + return &allocsThisZoneSinceMinorGC_; + } + + uint32_t getAndResetAllocsThisZoneSinceMinorGC() { + uint32_t allocs = allocsThisZoneSinceMinorGC_; + allocsThisZoneSinceMinorGC_ = 0; + return allocs; + } + + // Accessors for immutable runtime data. + JSAtomState& names() { return *runtime_->commonNames; } + js::frontend::WellKnownParserAtoms& parserNames() { + return *runtime_->commonParserNames; + } + js::StaticStrings& staticStrings() { return *runtime_->staticStrings; } + js::SharedImmutableStringsCache& sharedImmutableStrings() { + return runtime_->sharedImmutableStrings(); + } + bool permanentAtomsPopulated() { return runtime_->permanentAtomsPopulated(); } + const js::FrozenAtomSet& permanentAtoms() { + return *runtime_->permanentAtoms(); + } + js::WellKnownSymbols& wellKnownSymbols() { + return *runtime_->wellKnownSymbols; + } + js::PropertyName* emptyString() { return runtime_->emptyString; } + JSFreeOp* defaultFreeOp() { return &defaultFreeOp_.ref(); } + uintptr_t stackLimit(JS::StackKind kind) { return nativeStackLimit[kind]; } + uintptr_t stackLimitForJitCode(JS::StackKind kind); + size_t gcSystemPageSize() { return js::gc::SystemPageSize(); } + + /* + * "Entering" a realm changes cx->realm (which changes cx->global). Note + * that this does not push an Activation so it's possible for the caller's + * realm to be != cx->realm(). This is not a problem since, in general, most + * places in the VM cannot know that they were called from script (e.g., + * they may have been called through the JSAPI via JS_CallFunction) and thus + * cannot expect there is a scripted caller. + * + * Realms should be entered/left in a LIFO fasion. To enter a realm, code + * should prefer using AutoRealm over JS::EnterRealm/JS::LeaveRealm. + * + * Also note that the JIT can enter (same-compartment) realms without going + * through these methods - it will update cx->realm_ directly. + */ + private: + inline void setRealm(JS::Realm* realm); + inline void enterRealm(JS::Realm* realm); + + inline void enterAtomsZone(); + inline void leaveAtomsZone(JS::Realm* oldRealm); + enum IsAtomsZone { AtomsZone, NotAtomsZone }; + inline void setZone(js::Zone* zone, IsAtomsZone isAtomsZone); + + friend class js::AutoAllocInAtomsZone; + friend class js::AutoMaybeLeaveAtomsZone; + friend class js::AutoRealm; + + public: + inline void enterRealmOf(JSObject* target); + inline void enterRealmOf(JSScript* target); + inline void enterRealmOf(js::ObjectGroup* target); + inline void enterNullRealm(); + + inline void setRealmForJitExceptionHandler(JS::Realm* realm); + + inline void leaveRealm(JS::Realm* oldRealm); + + void setParseTask(js::ParseTask* parseTask) { parseTask_ = parseTask; } + js::ParseTask* parseTask() const { return parseTask_; } + + bool isNurseryAllocSuppressed() const { return nurserySuppressions_; } + + // Threads may freely access any data in their realm, compartment and zone. + JS::Compartment* compartment() const { + return realm_ ? JS::GetCompartmentForRealm(realm_) : nullptr; + } + + JS::Realm* realm() const { return realm_; } + +#ifdef DEBUG + bool inAtomsZone() const; +#endif + + JS::Zone* zone() const { + MOZ_ASSERT_IF(!realm() && zone_, inAtomsZone()); + MOZ_ASSERT_IF(realm(), js::GetRealmZone(realm()) == zone_); + return zoneRaw(); + } + + // For use when the context's zone is being read by another thread and the + // compartment and zone pointers might not be in sync. + JS::Zone* zoneRaw() const { return zone_; } + + // For JIT use. + static size_t offsetOfZone() { return offsetof(JSContext, zone_); } + + // Current global. This is only safe to use within the scope of the + // AutoRealm from which it's called. + inline js::Handle<js::GlobalObject*> global() const; + + js::AtomsTable& atoms() { return runtime_->atoms(); } + + const JS::Zone* atomsZone(const js::AutoAccessAtomsZone& access) { + return runtime_->atomsZone(access); + } + + js::SymbolRegistry& symbolRegistry() { return runtime_->symbolRegistry(); } + + // Methods to access runtime data that must be protected by locks. + js::SharedImmutableScriptDataTable& scriptDataTable( + js::AutoLockScriptData& lock) { + return runtime_->scriptDataTable(lock); + } + + // Methods to access other runtime data that checks locking internally. + js::gc::AtomMarkingRuntime& atomMarking() { return runtime_->gc.atomMarking; } + void markAtom(JSAtom* atom) { atomMarking().markAtom(this, atom); } + void markAtom(JS::Symbol* symbol) { atomMarking().markAtom(this, symbol); } + void markId(jsid id) { atomMarking().markId(this, id); } + void markAtomValue(const js::Value& value) { + atomMarking().markAtomValue(this, value); + } + + // Methods specific to any HelperThread for the context. + bool addPendingCompileError(js::CompileError** err); + void addPendingOverRecursed(); + void addPendingOutOfMemory(); + + bool isCompileErrorPending() const; + + JSRuntime* runtime() { return runtime_; } + const JSRuntime* runtime() const { return runtime_; } + + static size_t offsetOfRealm() { return offsetof(JSContext, realm_); } + + friend class JS::AutoSaveExceptionState; + friend class js::jit::DebugModeOSRVolatileJitFrameIter; + friend void js::ReportOverRecursed(JSContext*, unsigned errorNumber); + + public: + inline JS::Result<> boolToResult(bool ok); + + /** + * Intentionally awkward signpost method that is stationed on the + * boundary between Result-using and non-Result-using code. + */ + template <typename V, typename E> + bool resultToBool(const JS::Result<V, E>& result) { + return result.isOk(); + } + + template <typename V, typename E> + V* resultToPtr(const JS::Result<V*, E>& result) { + return result.isOk() ? result.unwrap() : nullptr; + } + + mozilla::GenericErrorResult<JS::OOM> alreadyReportedOOM(); + mozilla::GenericErrorResult<JS::Error> alreadyReportedError(); + + /* + * Points to the most recent JitActivation pushed on the thread. + * See JitActivation constructor in vm/Stack.cpp + */ + js::ContextData<js::jit::JitActivation*> jitActivation; + + // Shim for V8 interfaces used by irregexp code + js::ContextData<js::irregexp::Isolate*> isolate; + + /* + * Points to the most recent activation running on the thread. + * See Activation comment in vm/Stack.h. + */ + js::ContextData<js::Activation*> activation_; + + /* + * Points to the most recent profiling activation running on the + * thread. + */ + js::Activation* volatile profilingActivation_; + + public: + js::Activation* activation() const { return activation_; } + static size_t offsetOfActivation() { + return offsetof(JSContext, activation_); + } + + js::Activation* profilingActivation() const { return profilingActivation_; } + static size_t offsetOfProfilingActivation() { + return offsetof(JSContext, profilingActivation_); + } + + static size_t offsetOfJitActivation() { + return offsetof(JSContext, jitActivation); + } + +#ifdef DEBUG + static size_t offsetOfInUnsafeCallWithABI() { + return offsetof(JSContext, inUnsafeCallWithABI); + } +#endif + + static size_t offsetOfInlinedICScript() { + return offsetof(JSContext, inlinedICScript_); + } + + public: + js::InterpreterStack& interpreterStack() { + return runtime()->interpreterStack(); + } + + /* Base address of the native stack for the current thread. */ + uintptr_t nativeStackBase; + + public: + /* If non-null, report JavaScript entry points to this monitor. */ + js::ContextData<JS::dbg::AutoEntryMonitor*> entryMonitor; + + /* + * Stack of debuggers that currently disallow debuggee execution. + * + * When we check for NX we are inside the debuggee compartment, and thus a + * stack of Debuggers that have prevented execution need to be tracked to + * enter the correct Debugger compartment to report the error. + */ + js::ContextData<js::EnterDebuggeeNoExecute*> noExecuteDebuggerTop; + +#ifdef DEBUG + js::ContextData<uint32_t> inUnsafeCallWithABI; + js::ContextData<bool> hasAutoUnsafeCallWithABI; +#endif + +#ifdef JS_SIMULATOR + private: + js::ContextData<js::jit::Simulator*> simulator_; + + public: + js::jit::Simulator* simulator() const; + uintptr_t* addressOfSimulatorStackLimit(); +#endif + +#ifdef JS_TRACE_LOGGING + js::UnprotectedData<js::TraceLoggerThread*> traceLogger; +#endif + + public: + // State used by util/DoubleToString.cpp. + js::ContextData<DtoaState*> dtoaState; + + /* + * When this flag is non-zero, any attempt to GC will be skipped. See the + * AutoSuppressGC class for for details. + */ + js::ContextData<int32_t> suppressGC; + + // clang-format off + enum class GCUse { + // This thread is not running in the garbage collector. + None, + + // This thread is currently marking GC things. This thread could be the main + // thread or a helper thread doing sweep-marking. + Marking, + + // This thread is currently sweeping GC things. This thread could be the + // main thread or a helper thread while the main thread is running the + // mutator. + Sweeping, + + // Whether this thread is currently finalizing GC things. This thread could + // be the main thread or a helper thread doing finalization while the main + // thread is running the mutator. + Finalizing + }; + // clang-format on + +#ifdef DEBUG + // Which part of the garbage collector this context is running at the moment. + js::ContextData<GCUse> gcUse; + + // The specific zone currently being swept, if any. + js::ContextData<JS::Zone*> gcSweepZone; + + // Whether this thread is currently manipulating possibly-gray GC things. + js::ContextData<size_t> isTouchingGrayThings; + + js::ContextData<size_t> noNurseryAllocationCheck; + + /* + * If this is 0, all cross-compartment proxies must be registered in the + * wrapper map. This checking must be disabled temporarily while creating + * new wrappers. When non-zero, this records the recursion depth of wrapper + * creation. + */ + js::ContextData<uintptr_t> disableStrictProxyCheckingCount; + + bool isNurseryAllocAllowed() { return noNurseryAllocationCheck == 0; } + void disallowNurseryAlloc() { ++noNurseryAllocationCheck; } + void allowNurseryAlloc() { + MOZ_ASSERT(!isNurseryAllocAllowed()); + --noNurseryAllocationCheck; + } + + bool isStrictProxyCheckingEnabled() { + return disableStrictProxyCheckingCount == 0; + } + void disableStrictProxyChecking() { ++disableStrictProxyCheckingCount; } + void enableStrictProxyChecking() { + MOZ_ASSERT(disableStrictProxyCheckingCount > 0); + --disableStrictProxyCheckingCount; + } +#endif + +#if defined(DEBUG) || defined(JS_OOM_BREAKPOINT) + // We are currently running a simulated OOM test. + js::ContextData<bool> runningOOMTest; +#endif + + /* + * Some regions of code are hard for the static rooting hazard analysis to + * understand. In those cases, we trade the static analysis for a dynamic + * analysis. When this is non-zero, we should assert if we trigger, or + * might trigger, a GC. + */ + js::ContextData<int> inUnsafeRegion; + + // Count of AutoDisableGenerationalGC instances on the thread's stack. + js::ContextData<unsigned> generationalDisabled; + + // Some code cannot tolerate compacting GC so it can be disabled temporarily + // with AutoDisableCompactingGC which uses this counter. + js::ContextData<unsigned> compactingDisabledCount; + + bool canCollectAtoms() const { + // TODO: We may be able to improve this by collecting if + // !isOffThreadParseRunning() (bug 1468422). + return !runtime()->hasHelperThreadZones(); + } + + private: + // Pools used for recycling name maps and vectors when parsing and + // emitting bytecode. Purged on GC when there are no active script + // compilations. + js::ContextData<js::frontend::NameCollectionPool> frontendCollectionPool_; + + public: + js::frontend::NameCollectionPool& frontendCollectionPool() { + return frontendCollectionPool_.ref(); + } + + void verifyIsSafeToGC() { + MOZ_DIAGNOSTIC_ASSERT(!inUnsafeRegion, + "[AutoAssertNoGC] possible GC in GC-unsafe region"); + } + + /* Whether sampling should be enabled or not. */ + private: + mozilla::Atomic<bool, mozilla::SequentiallyConsistent> + suppressProfilerSampling; + + public: + bool isProfilerSamplingEnabled() const { return !suppressProfilerSampling; } + void disableProfilerSampling() { suppressProfilerSampling = true; } + void enableProfilerSampling() { suppressProfilerSampling = false; } + + private: + js::wasm::Context wasm_; + + public: + js::wasm::Context& wasm() { return wasm_; } + + /* Temporary arena pool used while compiling and decompiling. */ + static const size_t TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE = 4 * 1024; + + private: + js::ContextData<js::LifoAlloc> tempLifoAlloc_; + + public: + js::LifoAlloc& tempLifoAlloc() { return tempLifoAlloc_.ref(); } + const js::LifoAlloc& tempLifoAlloc() const { return tempLifoAlloc_.ref(); } + js::LifoAlloc& tempLifoAllocNoCheck() { return tempLifoAlloc_.refNoCheck(); } + + js::ContextData<uint32_t> debuggerMutations; + + // Cache for jit::GetPcScript(). + js::ContextData<js::UniquePtr<js::jit::PcScriptCache>> ionPcScriptCache; + + private: + /* Exception state -- the exception member is a GC root by definition. */ + js::ContextData<bool> throwing; /* is there a pending exception? */ + js::ContextData<JS::PersistentRooted<JS::Value>> + unwrappedException_; /* most-recently-thrown exception */ + js::ContextData<JS::PersistentRooted<js::SavedFrame*>> + unwrappedExceptionStack_; /* stack when the exception was thrown */ + + JS::Value& unwrappedException() { + if (!unwrappedException_.ref().initialized()) { + unwrappedException_.ref().init(this); + } + return unwrappedException_.ref().get(); + } + + js::SavedFrame*& unwrappedExceptionStack() { + if (!unwrappedExceptionStack_.ref().initialized()) { + unwrappedExceptionStack_.ref().init(this); + } + return unwrappedExceptionStack_.ref().get(); + } + + // True if the exception currently being thrown is by result of + // ReportOverRecursed. See Debugger::slowPathOnExceptionUnwind. + js::ContextData<bool> overRecursed_; + +#ifdef DEBUG + // True if this context has ever called ReportOverRecursed. + js::ContextData<bool> hadOverRecursed_; + + public: + bool hadNondeterministicException() const { + return hadOverRecursed_ || runtime()->hadOutOfMemory; + } +#endif + + private: + // True if propagating a forced return from an interrupt handler during + // debug mode. + js::ContextData<bool> propagatingForcedReturn_; + + public: + js::ContextData<int32_t> reportGranularity; /* see vm/Probes.h */ + + js::ContextData<js::AutoResolving*> resolvingList; + +#ifdef DEBUG + js::ContextData<js::AutoEnterPolicy*> enteredPolicy; +#endif + + /* True if generating an error, to prevent runaway recursion. */ + js::ContextData<bool> generatingError; + + private: + /* State for object and array toSource conversion. */ + js::ContextData<js::AutoCycleDetector::Vector> cycleDetectorVector_; + + public: + js::AutoCycleDetector::Vector& cycleDetectorVector() { + return cycleDetectorVector_.ref(); + } + const js::AutoCycleDetector::Vector& cycleDetectorVector() const { + return cycleDetectorVector_.ref(); + } + + /* Client opaque pointer. */ + js::UnprotectedData<void*> data; + + void initJitStackLimit(); + void resetJitStackLimit(); + + public: + JS::ContextOptions& options() { return options_.ref(); } + + bool runtimeMatches(JSRuntime* rt) const { return runtime_ == rt; } + + private: + /* + * Youngest frame of a saved stack that will be picked up as an async stack + * by any new Activation, and is nullptr when no async stack should be used. + * + * The JS::AutoSetAsyncStackForNewCalls class can be used to set this. + * + * New activations will reset this to nullptr on construction after getting + * the current value, and will restore the previous value on destruction. + */ + js::ContextData<JS::PersistentRooted<js::SavedFrame*>> + asyncStackForNewActivations_; + + public: + js::SavedFrame*& asyncStackForNewActivations() { + if (!asyncStackForNewActivations_.ref().initialized()) { + asyncStackForNewActivations_.ref().init(this); + } + return asyncStackForNewActivations_.ref().get(); + } + + /* + * Value of asyncCause to be attached to asyncStackForNewActivations. + */ + js::ContextData<const char*> asyncCauseForNewActivations; + + /* + * True if the async call was explicitly requested, e.g. via + * callFunctionWithAsyncStack. + */ + js::ContextData<bool> asyncCallIsExplicit; + + bool currentlyRunningInInterpreter() const { + return activation()->isInterpreter(); + } + bool currentlyRunningInJit() const { return activation()->isJit(); } + js::InterpreterFrame* interpreterFrame() const { + return activation()->asInterpreter()->current(); + } + js::InterpreterRegs& interpreterRegs() const { + return activation()->asInterpreter()->regs(); + } + + /* + * Get the topmost script and optional pc on the stack. By default, this + * function only returns a JSScript in the current realm, returning nullptr + * if the current script is in a different realm. This behavior can be + * overridden by passing AllowCrossRealm::Allow. + */ + enum class AllowCrossRealm { DontAllow = false, Allow = true }; + inline JSScript* currentScript( + jsbytecode** pc = nullptr, + AllowCrossRealm allowCrossRealm = AllowCrossRealm::DontAllow) const; + + inline js::Nursery& nursery(); + inline void minorGC(JS::GCReason reason); + + public: + bool isExceptionPending() const { return throwing; } + + MOZ_MUST_USE + bool getPendingException(JS::MutableHandleValue rval); + + js::SavedFrame* getPendingExceptionStack(); + + bool isThrowingOutOfMemory(); + bool isThrowingDebuggeeWouldRun(); + bool isClosingGenerator(); + + void setPendingException(JS::HandleValue v, js::HandleSavedFrame stack); + void setPendingExceptionAndCaptureStack(JS::HandleValue v); + + void clearPendingException() { + throwing = false; + overRecursed_ = false; + unwrappedException().setUndefined(); + unwrappedExceptionStack() = nullptr; + } + + bool isThrowingOverRecursed() const { return throwing && overRecursed_; } + bool isPropagatingForcedReturn() const { return propagatingForcedReturn_; } + void setPropagatingForcedReturn() { propagatingForcedReturn_ = true; } + void clearPropagatingForcedReturn() { propagatingForcedReturn_ = false; } + + /* + * See JS_SetTrustedPrincipals in jsapi.h. + * Note: !cx->realm() is treated as trusted. + */ + inline bool runningWithTrustedPrincipals(); + + size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const; + size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const; + + void trace(JSTracer* trc); + + inline js::RuntimeCaches& caches(); + + public: + using InterruptCallbackVector = + js::Vector<JSInterruptCallback, 2, js::SystemAllocPolicy>; + + private: + js::ContextData<InterruptCallbackVector> interruptCallbacks_; + + public: + InterruptCallbackVector& interruptCallbacks() { + return interruptCallbacks_.ref(); + } + + js::ContextData<bool> interruptCallbackDisabled; + + // Bitfield storing InterruptReason values. + mozilla::Atomic<uint32_t, mozilla::Relaxed> interruptBits_; + + // Any thread can call requestInterrupt() to request that this thread + // stop running. To stop this thread, requestInterrupt sets two fields: + // interruptBits_ (a bitset of InterruptReasons) and jitStackLimit_ (set to + // UINTPTR_MAX). The JS engine must continually poll one of these fields + // and call handleInterrupt if either field has the interrupt value. + // + // The point of setting jitStackLimit_ to UINTPTR_MAX is that JIT code + // already needs to guard on jitStackLimit_ in every function prologue to + // avoid stack overflow, so we avoid a second branch on interruptBits_ by + // setting jitStackLimit_ to a value that is guaranteed to fail the guard.) + // + // Note that the writes to interruptBits_ and jitStackLimit_ use a Relaxed + // Atomic so, while the writes are guaranteed to eventually be visible to + // this thread, it can happen in any order. handleInterrupt calls the + // interrupt callback if either is set, so it really doesn't matter as long + // as the JS engine is continually polling at least one field. In corner + // cases, this relaxed ordering could lead to an interrupt handler being + // called twice in succession after a single requestInterrupt call, but + // that's fine. + void requestInterrupt(js::InterruptReason reason); + bool handleInterrupt(); + + MOZ_ALWAYS_INLINE bool hasAnyPendingInterrupt() const { + static_assert(sizeof(interruptBits_) == sizeof(uint32_t), + "Assumed by JIT callers"); + return interruptBits_ != 0; + } + bool hasPendingInterrupt(js::InterruptReason reason) const { + return interruptBits_ & uint32_t(reason); + } + + // For JIT use. Points to the inlined ICScript for a baseline script + // being invoked as part of a trial inlining. Contains nullptr at + // all times except for the brief moment between being set in the + // caller and read in the callee's prologue. + js::ContextData<js::jit::ICScript*> inlinedICScript_; + + public: + void* addressOfInterruptBits() { return &interruptBits_; } + void* addressOfJitStackLimit() { return &jitStackLimit; } + void* addressOfJitStackLimitNoInterrupt() { + return &jitStackLimitNoInterrupt; + } + void* addressOfZone() { return &zone_; } + + const void* addressOfRealm() const { return &realm_; } + + void* addressOfInlinedICScript() { return &inlinedICScript_; } + + // Futex state, used by Atomics.wait() and Atomics.wake() on the Atomics + // object. + js::FutexThread fx; + + mozilla::Atomic<uintptr_t, mozilla::Relaxed> jitStackLimit; + + // Like jitStackLimit, but not reset to trigger interrupts. + js::ContextData<uintptr_t> jitStackLimitNoInterrupt; + + // Queue of pending jobs as described in ES2016 section 8.4. + // + // This is a non-owning pointer to either: + // - a JobQueue implementation the embedding provided by calling + // JS::SetJobQueue, owned by the embedding, or + // - our internal JobQueue implementation, established by calling + // js::UseInternalJobQueues, owned by JSContext::internalJobQueue below. + js::ContextData<JS::JobQueue*> jobQueue; + + // If the embedding has called js::UseInternalJobQueues, this is the owning + // pointer to our internal JobQueue implementation, which JSContext::jobQueue + // borrows. + js::ContextData<js::UniquePtr<js::InternalJobQueue>> internalJobQueue; + + // True if jobQueue is empty, or we are running the last job in the queue. + // Such conditions permit optimizations around `await` expressions. + js::ContextData<bool> canSkipEnqueuingJobs; + + js::ContextData<JS::PromiseRejectionTrackerCallback> + promiseRejectionTrackerCallback; + js::ContextData<void*> promiseRejectionTrackerCallbackData; + + JSObject* getIncumbentGlobal(JSContext* cx); + bool enqueuePromiseJob(JSContext* cx, js::HandleFunction job, + js::HandleObject promise, + js::HandleObject incumbentGlobal); + void addUnhandledRejectedPromise(JSContext* cx, js::HandleObject promise); + void removeUnhandledRejectedPromise(JSContext* cx, js::HandleObject promise); + + private: + template <class... Args> + inline void checkImpl(const Args&... args); + + bool contextChecksEnabled() const { + // Don't perform these checks when called from a finalizer. The checking + // depends on other objects not having been swept yet. + return !RuntimeHeapIsCollecting(runtime()->heapState()); + } + + public: + // Assert the arguments are in this context's realm (for scripts), + // compartment (for objects) or zone (for strings, symbols). + template <class... Args> + inline void check(const Args&... args); + template <class... Args> + inline void releaseCheck(const Args&... args); + template <class... Args> + MOZ_ALWAYS_INLINE void debugOnlyCheck(const Args&... args); + +#ifdef JS_STRUCTURED_SPEW + private: + // Spewer for this thread + js::UnprotectedData<js::StructuredSpewer> structuredSpewer_; + + public: + js::StructuredSpewer& spewer() { return structuredSpewer_.ref(); } +#endif + + // During debugger evaluations which need to observe native calls, JITs are + // completely disabled. This flag indicates whether we are in this state, and + // the debugger which initiated the evaluation. This debugger has other + // references on the stack and does not need to be traced. + js::ContextData<js::Debugger*> insideDebuggerEvaluationWithOnNativeCallHook; + +}; /* struct JSContext */ + +inline JS::Result<> JSContext::boolToResult(bool ok) { + if (MOZ_LIKELY(ok)) { + MOZ_ASSERT(!isExceptionPending()); + MOZ_ASSERT(!isPropagatingForcedReturn()); + return JS::Ok(); + } + return JS::Result<>(JS::Error()); +} + +inline JSContext* JSRuntime::mainContextFromOwnThread() { + MOZ_ASSERT(mainContextFromAnyThread() == js::TlsContext.get()); + return mainContextFromAnyThread(); +} + +namespace js { + +struct MOZ_RAII AutoResolving { + public: + enum Kind { LOOKUP, WATCH }; + + AutoResolving(JSContext* cx, HandleObject obj, HandleId id, + Kind kind = LOOKUP) + : context(cx), object(obj), id(id), kind(kind), link(cx->resolvingList) { + MOZ_ASSERT(obj); + cx->resolvingList = this; + } + + ~AutoResolving() { + MOZ_ASSERT(context->resolvingList == this); + context->resolvingList = link; + } + + bool alreadyStarted() const { return link && alreadyStartedSlow(); } + + private: + bool alreadyStartedSlow() const; + + JSContext* const context; + HandleObject object; + HandleId id; + Kind const kind; + AutoResolving* const link; +}; + +/* + * Create and destroy functions for JSContext, which is manually allocated + * and exclusively owned. + */ +extern JSContext* NewContext(uint32_t maxBytes, JSRuntime* parentRuntime); + +extern void DestroyContext(JSContext* cx); + +/* |callee| requires a usage string provided by JS_DefineFunctionsWithHelp. */ +extern void ReportUsageErrorASCII(JSContext* cx, HandleObject callee, + const char* msg); + +extern void ReportIsNotDefined(JSContext* cx, HandlePropertyName name); + +extern void ReportIsNotDefined(JSContext* cx, HandleId id); + +/* + * Report an attempt to access the property of a null or undefined value (v). + */ +extern void ReportIsNullOrUndefinedForPropertyAccess(JSContext* cx, + HandleValue v, int vIndex); +extern void ReportIsNullOrUndefinedForPropertyAccess(JSContext* cx, + HandleValue v, int vIndex, + HandleId key); + +/* + * Report error using js::DecompileValueGenerator(cx, spindex, v, fallback) as + * the first argument for the error message. + */ +extern bool ReportValueError(JSContext* cx, const unsigned errorNumber, + int spindex, HandleValue v, HandleString fallback, + const char* arg1 = nullptr, + const char* arg2 = nullptr); + +JSObject* CreateErrorNotesArray(JSContext* cx, JSErrorReport* report); + +/************************************************************************/ + +/* + * Encapsulates an external array of values and adds a trace method, for use in + * Rooted. + */ +class MOZ_STACK_CLASS ExternalValueArray { + public: + ExternalValueArray(size_t len, Value* vec) : array_(vec), length_(len) {} + + Value* begin() { return array_; } + size_t length() { return length_; } + + void trace(JSTracer* trc); + + private: + Value* array_; + size_t length_; +}; + +/* RootedExternalValueArray roots an external array of Values. */ +class MOZ_RAII RootedExternalValueArray + : public JS::Rooted<ExternalValueArray> { + public: + RootedExternalValueArray(JSContext* cx, size_t len, Value* vec) + : JS::Rooted<ExternalValueArray>(cx, ExternalValueArray(len, vec)) {} + + private: +}; + +class AutoAssertNoPendingException { +#ifdef DEBUG + JSContext* cx_; + + public: + explicit AutoAssertNoPendingException(JSContext* cxArg) : cx_(cxArg) { + MOZ_ASSERT(!JS_IsExceptionPending(cx_)); + } + + ~AutoAssertNoPendingException() { MOZ_ASSERT(!JS_IsExceptionPending(cx_)); } +#else + public: + explicit AutoAssertNoPendingException(JSContext* cxArg) {} +#endif +}; + +class MOZ_RAII AutoLockScriptData { + JSRuntime* runtime; + + public: + explicit AutoLockScriptData(JSRuntime* rt) { + MOZ_ASSERT(CurrentThreadCanAccessRuntime(rt) || + CurrentThreadIsParseThread()); + runtime = rt; + if (runtime->hasParseTasks()) { + runtime->scriptDataLock.lock(); + } else { + MOZ_ASSERT(!runtime->activeThreadHasScriptDataAccess); +#ifdef DEBUG + runtime->activeThreadHasScriptDataAccess = true; +#endif + } + } + ~AutoLockScriptData() { + if (runtime->hasParseTasks()) { + runtime->scriptDataLock.unlock(); + } else { + MOZ_ASSERT(runtime->activeThreadHasScriptDataAccess); +#ifdef DEBUG + runtime->activeThreadHasScriptDataAccess = false; +#endif + } + } +}; + +// A token used to prove you can safely access the atoms zone. This zone is +// accessed by the main thread and by off-thread parsing. There are two +// situations in which it is safe: +// +// - the current thread holds all atoms table locks (off-thread parsing may be +// running and must also take one of these locks for access) +// +// - the GC is running and is collecting the atoms zone (this cannot be started +// while off-thread parsing is happening) +class MOZ_STACK_CLASS AutoAccessAtomsZone { + public: + MOZ_IMPLICIT AutoAccessAtomsZone(const AutoLockAllAtoms& lock) {} + MOZ_IMPLICIT AutoAccessAtomsZone( + const gc::AutoCheckCanAccessAtomsDuringGC& canAccess) {} +}; + +class MOZ_RAII AutoNoteDebuggerEvaluationWithOnNativeCallHook { + JSContext* cx; + Debugger* oldValue; + + public: + AutoNoteDebuggerEvaluationWithOnNativeCallHook(JSContext* cx, Debugger* dbg) + : cx(cx), oldValue(cx->insideDebuggerEvaluationWithOnNativeCallHook) { + cx->insideDebuggerEvaluationWithOnNativeCallHook = dbg; + } + + ~AutoNoteDebuggerEvaluationWithOnNativeCallHook() { + cx->insideDebuggerEvaluationWithOnNativeCallHook = oldValue; + } +}; + +enum UnsafeABIStrictness { + NoExceptions, + AllowPendingExceptions, + AllowThrownExceptions +}; + +// Should be used in functions called directly from JIT code (with +// masm.callWithABI) to assert invariants in debug builds. +// In debug mode, masm.callWithABI inserts code to verify that the +// callee function uses AutoUnsafeCallWithABI. +// While this object is live: +// 1. cx->hasAutoUnsafeCallWithABI must be true. +// 2. We can't GC. +// 3. Exceptions should not be pending/thrown. +// +// Note that #3 is a precaution, not a requirement. By default, we +// assert that the function is not called with a pending exception, +// and that it does not throw an exception itself. +class MOZ_RAII AutoUnsafeCallWithABI { +#ifdef DEBUG + JSContext* cx_; + bool nested_; + bool checkForPendingException_; +#endif + JS::AutoCheckCannotGC nogc; + + public: +#ifdef DEBUG + explicit AutoUnsafeCallWithABI( + UnsafeABIStrictness strictness = UnsafeABIStrictness::NoExceptions); + ~AutoUnsafeCallWithABI(); +#else + explicit AutoUnsafeCallWithABI( + UnsafeABIStrictness unused_ = UnsafeABIStrictness::NoExceptions) {} +#endif +}; + +namespace gc { + +// Set/restore the performing GC flag for the current thread. +class MOZ_RAII AutoSetThreadIsPerformingGC { + JSContext* cx; + bool prev; + + public: + AutoSetThreadIsPerformingGC() + : cx(TlsContext.get()), prev(cx->defaultFreeOp()->isCollecting_) { + cx->defaultFreeOp()->isCollecting_ = true; + } + + ~AutoSetThreadIsPerformingGC() { cx->defaultFreeOp()->isCollecting_ = prev; } +}; + +struct MOZ_RAII AutoSetThreadGCUse { + protected: +#ifndef DEBUG + explicit AutoSetThreadGCUse(JSContext::GCUse use, Zone* sweepZone = nullptr) { + } +#else + explicit AutoSetThreadGCUse(JSContext::GCUse use, Zone* sweepZone = nullptr) + : cx(TlsContext.get()), prevUse(cx->gcUse), prevZone(cx->gcSweepZone) { + MOZ_ASSERT_IF(sweepZone, use == JSContext::GCUse::Sweeping); + cx->gcUse = use; + cx->gcSweepZone = sweepZone; + } + + ~AutoSetThreadGCUse() { + cx->gcUse = prevUse; + cx->gcSweepZone = prevZone; + MOZ_ASSERT_IF(cx->gcUse == JSContext::GCUse::None, !cx->gcSweepZone); + } + + private: + JSContext* cx; + JSContext::GCUse prevUse; + JS::Zone* prevZone; +#endif +}; + +// In debug builds, update the context state to indicate that the current thread +// is being used for GC marking. +struct MOZ_RAII AutoSetThreadIsMarking : public AutoSetThreadGCUse { + explicit AutoSetThreadIsMarking() + : AutoSetThreadGCUse(JSContext::GCUse::Marking) {} +}; + +// In debug builds, update the context state to indicate that the current thread +// is being used for GC sweeping. +struct MOZ_RAII AutoSetThreadIsSweeping : public AutoSetThreadGCUse { + explicit AutoSetThreadIsSweeping(Zone* zone = nullptr) + : AutoSetThreadGCUse(JSContext::GCUse::Sweeping, zone) {} +}; + +// In debug builds, update the context state to indicate that the current thread +// is being used for GC finalization. +struct MOZ_RAII AutoSetThreadIsFinalizing : public AutoSetThreadGCUse { + explicit AutoSetThreadIsFinalizing() + : AutoSetThreadGCUse(JSContext::GCUse::Finalizing) {} +}; + +// Note that this class does not suppress buffer allocation/reallocation in the +// nursery, only Cells themselves. +class MOZ_RAII AutoSuppressNurseryCellAlloc { + JSContext* cx_; + + public: + explicit AutoSuppressNurseryCellAlloc(JSContext* cx) : cx_(cx) { + cx_->nurserySuppressions_++; + } + ~AutoSuppressNurseryCellAlloc() { cx_->nurserySuppressions_--; } +}; + +} // namespace gc + +} /* namespace js */ + +#define CHECK_THREAD(cx) \ + MOZ_ASSERT_IF(cx, !cx->isHelperThreadContext() && \ + js::CurrentThreadCanAccessRuntime(cx->runtime())) + +#endif /* vm_JSContext_h */ |