/* -*- 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; 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; JS::PersistentRooted 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 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(JSRuntime* runtime, const JS::ContextOptions& options); ~JSContext(); bool init(js::ContextKind kind); private: js::UnprotectedData runtime_; js::WriteOnceData kind_; friend class js::gc::AutoSuppressNurseryCellAlloc; js::ContextData nurserySuppressions_; js::ContextData options_; // Free lists for allocating in the current zone. js::ContextData 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 atomsZoneFreeLists_; js::ContextData 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 freeUnusedMemory; // Are we currently timing execution? This flag ensures that we do not // double-count execution time in reentrant situations. js::ContextData 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 bool isInsideCurrentZone(T thing) const { return thing->zoneFromAnyThread() == zone_; } template 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 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 bool resultToBool(const JS::Result& result) { return result.isOk(); } template V* resultToPtr(const JS::Result& result) { return result.isOk() ? result.unwrap() : nullptr; } mozilla::GenericErrorResult alreadyReportedOOM(); mozilla::GenericErrorResult alreadyReportedError(); /* * Points to the most recent JitActivation pushed on the thread. * See JitActivation constructor in vm/Stack.cpp */ js::ContextData jitActivation; // Shim for V8 interfaces used by irregexp code js::ContextData isolate; /* * Points to the most recent activation running on the thread. * See Activation comment in vm/Stack.h. */ js::ContextData 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 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 noExecuteDebuggerTop; #ifdef DEBUG js::ContextData inUnsafeCallWithABI; js::ContextData hasAutoUnsafeCallWithABI; #endif #ifdef JS_SIMULATOR private: js::ContextData simulator_; public: js::jit::Simulator* simulator() const; uintptr_t* addressOfSimulatorStackLimit(); #endif #ifdef JS_TRACE_LOGGING js::UnprotectedData traceLogger; #endif public: // State used by util/DoubleToString.cpp. js::ContextData dtoaState; /* * When this flag is non-zero, any attempt to GC will be skipped. See the * AutoSuppressGC class for for details. */ js::ContextData 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; // The specific zone currently being swept, if any. js::ContextData gcSweepZone; // Whether this thread is currently manipulating possibly-gray GC things. js::ContextData isTouchingGrayThings; js::ContextData 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 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 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 inUnsafeRegion; // Count of AutoDisableGenerationalGC instances on the thread's stack. js::ContextData generationalDisabled; // Some code cannot tolerate compacting GC so it can be disabled temporarily // with AutoDisableCompactingGC which uses this counter. js::ContextData 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 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 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 tempLifoAlloc_; public: js::LifoAlloc& tempLifoAlloc() { return tempLifoAlloc_.ref(); } const js::LifoAlloc& tempLifoAlloc() const { return tempLifoAlloc_.ref(); } js::LifoAlloc& tempLifoAllocNoCheck() { return tempLifoAlloc_.refNoCheck(); } js::ContextData debuggerMutations; // Cache for jit::GetPcScript(). js::ContextData> ionPcScriptCache; private: /* Exception state -- the exception member is a GC root by definition. */ js::ContextData throwing; /* is there a pending exception? */ js::ContextData> unwrappedException_; /* most-recently-thrown exception */ js::ContextData> 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 overRecursed_; #ifdef DEBUG // True if this context has ever called ReportOverRecursed. js::ContextData 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 propagatingForcedReturn_; public: js::ContextData reportGranularity; /* see vm/Probes.h */ js::ContextData resolvingList; #ifdef DEBUG js::ContextData enteredPolicy; #endif /* True if generating an error, to prevent runaway recursion. */ js::ContextData generatingError; private: /* State for object and array toSource conversion. */ js::ContextData cycleDetectorVector_; public: js::AutoCycleDetector::Vector& cycleDetectorVector() { return cycleDetectorVector_.ref(); } const js::AutoCycleDetector::Vector& cycleDetectorVector() const { return cycleDetectorVector_.ref(); } /* Client opaque pointer. */ js::UnprotectedData 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> 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 asyncCauseForNewActivations; /* * True if the async call was explicitly requested, e.g. via * callFunctionWithAsyncStack. */ js::ContextData 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; private: js::ContextData interruptCallbacks_; public: InterruptCallbackVector& interruptCallbacks() { return interruptCallbacks_.ref(); } js::ContextData interruptCallbackDisabled; // Bitfield storing InterruptReason values. mozilla::Atomic 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 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 jitStackLimit; // Like jitStackLimit, but not reset to trigger interrupts. js::ContextData 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 jobQueue; // If the embedding has called js::UseInternalJobQueues, this is the owning // pointer to our internal JobQueue implementation, which JSContext::jobQueue // borrows. js::ContextData> 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 canSkipEnqueuingJobs; js::ContextData promiseRejectionTrackerCallback; js::ContextData 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 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 inline void check(const Args&... args); template inline void releaseCheck(const Args&... args); template MOZ_ALWAYS_INLINE void debugOnlyCheck(const Args&... args); #ifdef JS_STRUCTURED_SPEW private: // Spewer for this thread js::UnprotectedData 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 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 { public: RootedExternalValueArray(JSContext* cx, size_t len, Value* vec) : JS::Rooted(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 */