diff options
Diffstat (limited to 'xpcom/base/CycleCollectedJSContext.cpp')
-rw-r--r-- | xpcom/base/CycleCollectedJSContext.cpp | 1717 |
1 files changed, 1717 insertions, 0 deletions
diff --git a/xpcom/base/CycleCollectedJSContext.cpp b/xpcom/base/CycleCollectedJSContext.cpp new file mode 100644 index 0000000000..87e123078b --- /dev/null +++ b/xpcom/base/CycleCollectedJSContext.cpp @@ -0,0 +1,1717 @@ +/* -*- 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/. */ + +// We're dividing JS objects into 3 categories: +// +// 1. "real" roots, held by the JS engine itself or rooted through the root +// and lock JS APIs. Roots from this category are considered black in the +// cycle collector, any cycle they participate in is uncollectable. +// +// 2. certain roots held by C++ objects that are guaranteed to be alive. +// Roots from this category are considered black in the cycle collector, +// and any cycle they participate in is uncollectable. These roots are +// traced from TraceNativeBlackRoots. +// +// 3. all other roots held by C++ objects that participate in cycle +// collection, held by us (see TraceNativeGrayRoots). Roots from this +// category are considered grey in the cycle collector; whether or not +// they are collected depends on the objects that hold them. +// +// Note that if a root is in multiple categories the fact that it is in +// category 1 or 2 that takes precedence, so it will be considered black. +// +// During garbage collection we switch to an additional mark color (gray) +// when tracing inside TraceNativeGrayRoots. This allows us to walk those +// roots later on and add all objects reachable only from them to the +// cycle collector. +// +// Phases: +// +// 1. marking of the roots in category 1 by having the JS GC do its marking +// 2. marking of the roots in category 2 by having the JS GC call us back +// (via JS_SetExtraGCRootsTracer) and running TraceNativeBlackRoots +// 3. marking of the roots in category 3 by TraceNativeGrayRoots using an +// additional color (gray). +// 4. end of GC, GC can sweep its heap +// +// At some later point, when the cycle collector runs: +// +// 5. walk gray objects and add them to the cycle collector, cycle collect +// +// JS objects that are part of cycles the cycle collector breaks will be +// collected by the next JS GC. +// +// If WantAllTraces() is false the cycle collector will not traverse roots +// from category 1 or any JS objects held by them. Any JS objects they hold +// will already be marked by the JS GC and will thus be colored black +// themselves. Any C++ objects they hold will have a missing (untraversed) +// edge from the JS object to the C++ object and so it will be marked black +// too. This decreases the number of objects that the cycle collector has to +// deal with. +// To improve debugging, if WantAllTraces() is true all JS objects are +// traversed. + +#include "mozilla/CycleCollectedJSContext.h" +#include <algorithm> +#include "mozilla/ArrayUtils.h" +#include "mozilla/AutoRestore.h" +#include "mozilla/Move.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Sprintf.h" +#include "mozilla/Telemetry.h" +#include "mozilla/TimelineConsumers.h" +#include "mozilla/TimelineMarker.h" +#include "mozilla/Unused.h" +#include "mozilla/DebuggerOnGCRunnable.h" +#include "mozilla/dom/DOMJSClass.h" +#include "mozilla/dom/ProfileTimelineMarkerBinding.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/PromiseBinding.h" +#include "mozilla/dom/PromiseDebugging.h" +#include "mozilla/dom/ScriptSettings.h" +#include "jsprf.h" +#include "js/Debug.h" +#include "nsContentUtils.h" +#include "nsCycleCollectionNoteRootCallback.h" +#include "nsCycleCollectionParticipant.h" +#include "nsCycleCollector.h" +#include "nsDOMJSUtils.h" +#include "nsJSUtils.h" +#include "nsWrapperCache.h" + +#ifdef MOZ_CRASHREPORTER +#include "nsExceptionHandler.h" +#endif + +#include "nsIException.h" +#include "nsIPlatformInfo.h" +#include "nsThread.h" +#include "nsThreadUtils.h" +#include "xpcpublic.h" + +using namespace mozilla; +using namespace mozilla::dom; + +namespace mozilla { + +struct DeferredFinalizeFunctionHolder +{ + DeferredFinalizeFunction run; + void* data; +}; + +class IncrementalFinalizeRunnable : public Runnable +{ + typedef AutoTArray<DeferredFinalizeFunctionHolder, 16> DeferredFinalizeArray; + typedef CycleCollectedJSContext::DeferredFinalizerTable DeferredFinalizerTable; + + CycleCollectedJSContext* mContext; + DeferredFinalizeArray mDeferredFinalizeFunctions; + uint32_t mFinalizeFunctionToRun; + bool mReleasing; + + static const PRTime SliceMillis = 5; /* ms */ + +public: + IncrementalFinalizeRunnable(CycleCollectedJSContext* aCx, + DeferredFinalizerTable& aFinalizerTable); + virtual ~IncrementalFinalizeRunnable(); + + void ReleaseNow(bool aLimited); + + NS_DECL_NSIRUNNABLE +}; + +} // namespace mozilla + +struct NoteWeakMapChildrenTracer : public JS::CallbackTracer +{ + NoteWeakMapChildrenTracer(JSContext* aCx, + nsCycleCollectionNoteRootCallback& aCb) + : JS::CallbackTracer(aCx), mCb(aCb), mTracedAny(false), mMap(nullptr), + mKey(nullptr), mKeyDelegate(nullptr) + { + } + void onChild(const JS::GCCellPtr& aThing) override; + nsCycleCollectionNoteRootCallback& mCb; + bool mTracedAny; + JSObject* mMap; + JS::GCCellPtr mKey; + JSObject* mKeyDelegate; +}; + +void +NoteWeakMapChildrenTracer::onChild(const JS::GCCellPtr& aThing) +{ + if (aThing.is<JSString>()) { + return; + } + + if (!JS::GCThingIsMarkedGray(aThing) && !mCb.WantAllTraces()) { + return; + } + + if (AddToCCKind(aThing.kind())) { + mCb.NoteWeakMapping(mMap, mKey, mKeyDelegate, aThing); + mTracedAny = true; + } else { + JS::TraceChildren(this, aThing); + } +} + +struct NoteWeakMapsTracer : public js::WeakMapTracer +{ + NoteWeakMapsTracer(JSContext* aCx, nsCycleCollectionNoteRootCallback& aCccb) + : js::WeakMapTracer(aCx), mCb(aCccb), mChildTracer(aCx, aCccb) + { + } + void trace(JSObject* aMap, JS::GCCellPtr aKey, JS::GCCellPtr aValue) override; + nsCycleCollectionNoteRootCallback& mCb; + NoteWeakMapChildrenTracer mChildTracer; +}; + +void +NoteWeakMapsTracer::trace(JSObject* aMap, JS::GCCellPtr aKey, + JS::GCCellPtr aValue) +{ + // If nothing that could be held alive by this entry is marked gray, return. + if ((!aKey || !JS::GCThingIsMarkedGray(aKey)) && + MOZ_LIKELY(!mCb.WantAllTraces())) { + if (!aValue || !JS::GCThingIsMarkedGray(aValue) || aValue.is<JSString>()) { + return; + } + } + + // The cycle collector can only properly reason about weak maps if it can + // reason about the liveness of their keys, which in turn requires that + // the key can be represented in the cycle collector graph. All existing + // uses of weak maps use either objects or scripts as keys, which are okay. + MOZ_ASSERT(AddToCCKind(aKey.kind())); + + // As an emergency fallback for non-debug builds, if the key is not + // representable in the cycle collector graph, we treat it as marked. This + // can cause leaks, but is preferable to ignoring the binding, which could + // cause the cycle collector to free live objects. + if (!AddToCCKind(aKey.kind())) { + aKey = nullptr; + } + + JSObject* kdelegate = nullptr; + if (aKey.is<JSObject>()) { + kdelegate = js::GetWeakmapKeyDelegate(&aKey.as<JSObject>()); + } + + if (AddToCCKind(aValue.kind())) { + mCb.NoteWeakMapping(aMap, aKey, kdelegate, aValue); + } else { + mChildTracer.mTracedAny = false; + mChildTracer.mMap = aMap; + mChildTracer.mKey = aKey; + mChildTracer.mKeyDelegate = kdelegate; + + if (!aValue.is<JSString>()) { + JS::TraceChildren(&mChildTracer, aValue); + } + + // The delegate could hold alive the key, so report something to the CC + // if we haven't already. + if (!mChildTracer.mTracedAny && + aKey && JS::GCThingIsMarkedGray(aKey) && kdelegate) { + mCb.NoteWeakMapping(aMap, aKey, kdelegate, nullptr); + } + } +} + +// This is based on the logic in FixWeakMappingGrayBitsTracer::trace. +struct FixWeakMappingGrayBitsTracer : public js::WeakMapTracer +{ + explicit FixWeakMappingGrayBitsTracer(JSContext* aCx) + : js::WeakMapTracer(aCx) + { + } + + void + FixAll() + { + do { + mAnyMarked = false; + js::TraceWeakMaps(this); + } while (mAnyMarked); + } + + void trace(JSObject* aMap, JS::GCCellPtr aKey, JS::GCCellPtr aValue) override + { + // If nothing that could be held alive by this entry is marked gray, return. + bool keyMightNeedMarking = aKey && JS::GCThingIsMarkedGray(aKey); + bool valueMightNeedMarking = aValue && JS::GCThingIsMarkedGray(aValue) && + aValue.kind() != JS::TraceKind::String; + if (!keyMightNeedMarking && !valueMightNeedMarking) { + return; + } + + if (!AddToCCKind(aKey.kind())) { + aKey = nullptr; + } + + if (keyMightNeedMarking && aKey.is<JSObject>()) { + JSObject* kdelegate = js::GetWeakmapKeyDelegate(&aKey.as<JSObject>()); + if (kdelegate && !JS::ObjectIsMarkedGray(kdelegate) && + (!aMap || !JS::ObjectIsMarkedGray(aMap))) + { + if (JS::UnmarkGrayGCThingRecursively(aKey)) { + mAnyMarked = true; + } + } + } + + if (aValue && JS::GCThingIsMarkedGray(aValue) && + (!aKey || !JS::GCThingIsMarkedGray(aKey)) && + (!aMap || !JS::ObjectIsMarkedGray(aMap)) && + aValue.kind() != JS::TraceKind::Shape) { + if (JS::UnmarkGrayGCThingRecursively(aValue)) { + mAnyMarked = true; + } + } + } + + MOZ_INIT_OUTSIDE_CTOR bool mAnyMarked; +}; + +static void +CheckParticipatesInCycleCollection(JS::GCCellPtr aThing, const char* aName, + void* aClosure) +{ + bool* cycleCollectionEnabled = static_cast<bool*>(aClosure); + + if (*cycleCollectionEnabled) { + return; + } + + if (AddToCCKind(aThing.kind()) && JS::GCThingIsMarkedGray(aThing)) { + *cycleCollectionEnabled = true; + } +} + +NS_IMETHODIMP +JSGCThingParticipant::Traverse(void* aPtr, + nsCycleCollectionTraversalCallback& aCb) +{ + auto runtime = reinterpret_cast<CycleCollectedJSContext*>( + reinterpret_cast<char*>(this) - offsetof(CycleCollectedJSContext, + mGCThingCycleCollectorGlobal)); + + JS::GCCellPtr cellPtr(aPtr, JS::GCThingTraceKind(aPtr)); + runtime->TraverseGCThing(CycleCollectedJSContext::TRAVERSE_FULL, cellPtr, aCb); + return NS_OK; +} + +// NB: This is only used to initialize the participant in +// CycleCollectedJSContext. It should never be used directly. +static JSGCThingParticipant sGCThingCycleCollectorGlobal; + +NS_IMETHODIMP +JSZoneParticipant::Traverse(void* aPtr, nsCycleCollectionTraversalCallback& aCb) +{ + auto runtime = reinterpret_cast<CycleCollectedJSContext*>( + reinterpret_cast<char*>(this) - offsetof(CycleCollectedJSContext, + mJSZoneCycleCollectorGlobal)); + + MOZ_ASSERT(!aCb.WantAllTraces()); + JS::Zone* zone = static_cast<JS::Zone*>(aPtr); + + runtime->TraverseZone(zone, aCb); + return NS_OK; +} + +struct TraversalTracer : public JS::CallbackTracer +{ + TraversalTracer(JSContext* aCx, nsCycleCollectionTraversalCallback& aCb) + : JS::CallbackTracer(aCx, DoNotTraceWeakMaps), mCb(aCb) + { + } + void onChild(const JS::GCCellPtr& aThing) override; + nsCycleCollectionTraversalCallback& mCb; +}; + +void +TraversalTracer::onChild(const JS::GCCellPtr& aThing) +{ + // Don't traverse non-gray objects, unless we want all traces. + if (!JS::GCThingIsMarkedGray(aThing) && !mCb.WantAllTraces()) { + return; + } + + /* + * This function needs to be careful to avoid stack overflow. Normally, when + * AddToCCKind is true, the recursion terminates immediately as we just add + * |thing| to the CC graph. So overflow is only possible when there are long + * or cyclic chains of non-AddToCCKind GC things. Places where this can occur + * use special APIs to handle such chains iteratively. + */ + if (AddToCCKind(aThing.kind())) { + if (MOZ_UNLIKELY(mCb.WantDebugInfo())) { + char buffer[200]; + getTracingEdgeName(buffer, sizeof(buffer)); + mCb.NoteNextEdgeName(buffer); + } + mCb.NoteJSChild(aThing); + } else if (aThing.is<js::Shape>()) { + // The maximum depth of traversal when tracing a Shape is unbounded, due to + // the parent pointers on the shape. + JS_TraceShapeCycleCollectorChildren(this, aThing); + } else if (aThing.is<js::ObjectGroup>()) { + // The maximum depth of traversal when tracing an ObjectGroup is unbounded, + // due to information attached to the groups which can lead other groups to + // be traced. + JS_TraceObjectGroupCycleCollectorChildren(this, aThing); + } else if (!aThing.is<JSString>()) { + JS::TraceChildren(this, aThing); + } +} + +static void +NoteJSChildGrayWrapperShim(void* aData, JS::GCCellPtr aThing) +{ + TraversalTracer* trc = static_cast<TraversalTracer*>(aData); + trc->onChild(aThing); +} + +/* + * The cycle collection participant for a Zone is intended to produce the same + * results as if all of the gray GCthings in a zone were merged into a single node, + * except for self-edges. This avoids the overhead of representing all of the GCthings in + * the zone in the cycle collector graph, which should be much faster if many of + * the GCthings in the zone are gray. + * + * Zone merging should not always be used, because it is a conservative + * approximation of the true cycle collector graph that can incorrectly identify some + * garbage objects as being live. For instance, consider two cycles that pass through a + * zone, where one is garbage and the other is live. If we merge the entire + * zone, the cycle collector will think that both are alive. + * + * We don't have to worry about losing track of a garbage cycle, because any such garbage + * cycle incorrectly identified as live must contain at least one C++ to JS edge, and + * XPConnect will always add the C++ object to the CC graph. (This is in contrast to pure + * C++ garbage cycles, which must always be properly identified, because we clear the + * purple buffer during every CC, which may contain the last reference to a garbage + * cycle.) + */ + +// NB: This is only used to initialize the participant in +// CycleCollectedJSContext. It should never be used directly. +static const JSZoneParticipant sJSZoneCycleCollectorGlobal; + +static +void JSObjectsTenuredCb(JSContext* aContext, void* aData) +{ + static_cast<CycleCollectedJSContext*>(aData)->JSObjectsTenured(); +} + +bool +mozilla::GetBuildId(JS::BuildIdCharVector* aBuildID) +{ + nsCOMPtr<nsIPlatformInfo> info = do_GetService("@mozilla.org/xre/app-info;1"); + if (!info) { + return false; + } + + nsCString buildID; + nsresult rv = info->GetPlatformBuildID(buildID); + NS_ENSURE_SUCCESS(rv, false); + + if (!aBuildID->resize(buildID.Length())) { + return false; + } + + for (size_t i = 0; i < buildID.Length(); i++) { + (*aBuildID)[i] = buildID[i]; + } + + return true; +} + +CycleCollectedJSContext::CycleCollectedJSContext() + : mGCThingCycleCollectorGlobal(sGCThingCycleCollectorGlobal) + , mJSZoneCycleCollectorGlobal(sJSZoneCycleCollectorGlobal) + , mJSContext(nullptr) + , mPrevGCSliceCallback(nullptr) + , mPrevGCNurseryCollectionCallback(nullptr) + , mJSHolders(256) + , mDoingStableStates(false) + , mDisableMicroTaskCheckpoint(false) + , mOutOfMemoryState(OOMState::OK) + , mLargeAllocationFailureState(OOMState::OK) +{ + nsCOMPtr<nsIThread> thread = do_GetCurrentThread(); + mOwningThread = thread.forget().downcast<nsThread>().take(); + MOZ_RELEASE_ASSERT(mOwningThread); +} + +CycleCollectedJSContext::~CycleCollectedJSContext() +{ + // If the allocation failed, here we are. + if (!mJSContext) { + return; + } + + MOZ_ASSERT(!mDeferredFinalizerTable.Count()); + + // Last chance to process any events. + ProcessMetastableStateQueue(mBaseRecursionDepth); + MOZ_ASSERT(mMetastableStateEvents.IsEmpty()); + + ProcessStableStateQueue(); + MOZ_ASSERT(mStableStateEvents.IsEmpty()); + + // Clear mPendingException first, since it might be cycle collected. + mPendingException = nullptr; + + MOZ_ASSERT(mDebuggerPromiseMicroTaskQueue.empty()); + MOZ_ASSERT(mPromiseMicroTaskQueue.empty()); + +#ifdef SPIDERMONKEY_PROMISE + mUncaughtRejections.reset(); + mConsumedRejections.reset(); +#endif // SPIDERMONKEY_PROMISE + + JS_DestroyContext(mJSContext); + mJSContext = nullptr; + nsCycleCollector_forgetJSContext(); + + mozilla::dom::DestroyScriptSettings(); + + mOwningThread->SetScriptObserver(nullptr); + NS_RELEASE(mOwningThread); +} + +static void +MozCrashWarningReporter(JSContext*, JSErrorReport*) +{ + MOZ_CRASH("Why is someone touching JSAPI without an AutoJSAPI?"); +} + +nsresult +CycleCollectedJSContext::Initialize(JSContext* aParentContext, + uint32_t aMaxBytes, + uint32_t aMaxNurseryBytes) +{ + MOZ_ASSERT(!mJSContext); + + mOwningThread->SetScriptObserver(this); + // The main thread has a base recursion depth of 0, workers of 1. + mBaseRecursionDepth = RecursionDepth(); + + mozilla::dom::InitScriptSettings(); + mJSContext = JS_NewContext(aMaxBytes, aMaxNurseryBytes, aParentContext); + if (!mJSContext) { + return NS_ERROR_OUT_OF_MEMORY; + } + + NS_GetCurrentThread()->SetCanInvokeJS(true); + + if (!JS_AddExtraGCRootsTracer(mJSContext, TraceBlackJS, this)) { + MOZ_CRASH("JS_AddExtraGCRootsTracer failed"); + } + JS_SetGrayGCRootsTracer(mJSContext, TraceGrayJS, this); + JS_SetGCCallback(mJSContext, GCCallback, this); + mPrevGCSliceCallback = JS::SetGCSliceCallback(mJSContext, GCSliceCallback); + + if (NS_IsMainThread()) { + // We would like to support all threads here, but the way timeline consumers + // are set up currently, you can either add a marker for one specific + // docshell, or for every consumer globally. We would like to add a marker + // for every consumer observing anything on this thread, but that is not + // currently possible. For now, add global markers only when we are on the + // main thread, since the UI for this tracing data only displays data + // relevant to the main-thread. + mPrevGCNurseryCollectionCallback = JS::SetGCNurseryCollectionCallback( + mJSContext, GCNurseryCollectionCallback); + } + + JS_SetObjectsTenuredCallback(mJSContext, JSObjectsTenuredCb, this); + JS::SetOutOfMemoryCallback(mJSContext, OutOfMemoryCallback, this); + JS::SetLargeAllocationFailureCallback(mJSContext, + LargeAllocationFailureCallback, this); + JS_SetDestroyZoneCallback(mJSContext, XPCStringConvert::FreeZoneCache); + JS_SetSweepZoneCallback(mJSContext, XPCStringConvert::ClearZoneCache); + JS::SetBuildIdOp(mJSContext, GetBuildId); + JS::SetWarningReporter(mJSContext, MozCrashWarningReporter); +#ifdef MOZ_CRASHREPORTER + js::AutoEnterOOMUnsafeRegion::setAnnotateOOMAllocationSizeCallback( + CrashReporter::AnnotateOOMAllocationSize); +#endif + + static js::DOMCallbacks DOMcallbacks = { + InstanceClassHasProtoAtDepth + }; + SetDOMCallbacks(mJSContext, &DOMcallbacks); + js::SetScriptEnvironmentPreparer(mJSContext, &mEnvironmentPreparer); + + JS::SetGetIncumbentGlobalCallback(mJSContext, GetIncumbentGlobalCallback); + +#ifdef SPIDERMONKEY_PROMISE + JS::SetEnqueuePromiseJobCallback(mJSContext, EnqueuePromiseJobCallback, this); + JS::SetPromiseRejectionTrackerCallback(mJSContext, PromiseRejectionTrackerCallback, this); + mUncaughtRejections.init(mJSContext, JS::GCVector<JSObject*, 0, js::SystemAllocPolicy>(js::SystemAllocPolicy())); + mConsumedRejections.init(mJSContext, JS::GCVector<JSObject*, 0, js::SystemAllocPolicy>(js::SystemAllocPolicy())); +#endif // SPIDERMONKEY_PROMISE + + JS::dbg::SetDebuggerMallocSizeOf(mJSContext, moz_malloc_size_of); + + nsCycleCollector_registerJSContext(this); + + return NS_OK; +} + +size_t +CycleCollectedJSContext::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const +{ + size_t n = 0; + + // We're deliberately not measuring anything hanging off the entries in + // mJSHolders. + n += mJSHolders.ShallowSizeOfExcludingThis(aMallocSizeOf); + + return n; +} + +void +CycleCollectedJSContext::UnmarkSkippableJSHolders() +{ + for (auto iter = mJSHolders.Iter(); !iter.Done(); iter.Next()) { + void* holder = iter.Key(); + nsScriptObjectTracer*& tracer = iter.Data(); + tracer->CanSkip(holder, true); + } +} + +void +CycleCollectedJSContext::DescribeGCThing(bool aIsMarked, JS::GCCellPtr aThing, + nsCycleCollectionTraversalCallback& aCb) const +{ + if (!aCb.WantDebugInfo()) { + aCb.DescribeGCedNode(aIsMarked, "JS Object"); + return; + } + + char name[72]; + uint64_t compartmentAddress = 0; + if (aThing.is<JSObject>()) { + JSObject* obj = &aThing.as<JSObject>(); + compartmentAddress = (uint64_t)js::GetObjectCompartment(obj); + const js::Class* clasp = js::GetObjectClass(obj); + + // Give the subclass a chance to do something + if (DescribeCustomObjects(obj, clasp, name)) { + // Nothing else to do! + } else if (js::IsFunctionObject(obj)) { + JSFunction* fun = JS_GetObjectFunction(obj); + JSString* str = JS_GetFunctionDisplayId(fun); + if (str) { + JSFlatString* flat = JS_ASSERT_STRING_IS_FLAT(str); + nsAutoString chars; + AssignJSFlatString(chars, flat); + NS_ConvertUTF16toUTF8 fname(chars); + SprintfLiteral(name, "JS Object (Function - %s)", fname.get()); + } else { + SprintfLiteral(name, "JS Object (Function)"); + } + } else { + SprintfLiteral(name, "JS Object (%s)", clasp->name); + } + } else { + SprintfLiteral(name, "JS %s", JS::GCTraceKindToAscii(aThing.kind())); + } + + // Disable printing global for objects while we figure out ObjShrink fallout. + aCb.DescribeGCedNode(aIsMarked, name, compartmentAddress); +} + +void +CycleCollectedJSContext::NoteGCThingJSChildren(JS::GCCellPtr aThing, + nsCycleCollectionTraversalCallback& aCb) const +{ + MOZ_ASSERT(mJSContext); + TraversalTracer trc(mJSContext, aCb); + JS::TraceChildren(&trc, aThing); +} + +void +CycleCollectedJSContext::NoteGCThingXPCOMChildren(const js::Class* aClasp, + JSObject* aObj, + nsCycleCollectionTraversalCallback& aCb) const +{ + MOZ_ASSERT(aClasp); + MOZ_ASSERT(aClasp == js::GetObjectClass(aObj)); + + if (NoteCustomGCThingXPCOMChildren(aClasp, aObj, aCb)) { + // Nothing else to do! + return; + } + // XXX This test does seem fragile, we should probably whitelist classes + // that do hold a strong reference, but that might not be possible. + else if (aClasp->flags & JSCLASS_HAS_PRIVATE && + aClasp->flags & JSCLASS_PRIVATE_IS_NSISUPPORTS) { + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "js::GetObjectPrivate(obj)"); + aCb.NoteXPCOMChild(static_cast<nsISupports*>(js::GetObjectPrivate(aObj))); + } else { + const DOMJSClass* domClass = GetDOMClass(aObj); + if (domClass) { + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "UnwrapDOMObject(obj)"); + // It's possible that our object is an unforgeable holder object, in + // which case it doesn't actually have a C++ DOM object associated with + // it. Use UnwrapPossiblyNotInitializedDOMObject, which produces null in + // that case, since NoteXPCOMChild/NoteNativeChild are null-safe. + if (domClass->mDOMObjectIsISupports) { + aCb.NoteXPCOMChild(UnwrapPossiblyNotInitializedDOMObject<nsISupports>(aObj)); + } else if (domClass->mParticipant) { + aCb.NoteNativeChild(UnwrapPossiblyNotInitializedDOMObject<void>(aObj), + domClass->mParticipant); + } + } + } +} + +void +CycleCollectedJSContext::TraverseGCThing(TraverseSelect aTs, JS::GCCellPtr aThing, + nsCycleCollectionTraversalCallback& aCb) +{ + bool isMarkedGray = JS::GCThingIsMarkedGray(aThing); + + if (aTs == TRAVERSE_FULL) { + DescribeGCThing(!isMarkedGray, aThing, aCb); + } + + // If this object is alive, then all of its children are alive. For JS objects, + // the black-gray invariant ensures the children are also marked black. For C++ + // objects, the ref count from this object will keep them alive. Thus we don't + // need to trace our children, unless we are debugging using WantAllTraces. + if (!isMarkedGray && !aCb.WantAllTraces()) { + return; + } + + if (aTs == TRAVERSE_FULL) { + NoteGCThingJSChildren(aThing, aCb); + } + + if (aThing.is<JSObject>()) { + JSObject* obj = &aThing.as<JSObject>(); + NoteGCThingXPCOMChildren(js::GetObjectClass(obj), obj, aCb); + } +} + +struct TraverseObjectShimClosure +{ + nsCycleCollectionTraversalCallback& cb; + CycleCollectedJSContext* self; +}; + +void +CycleCollectedJSContext::TraverseZone(JS::Zone* aZone, + nsCycleCollectionTraversalCallback& aCb) +{ + MOZ_ASSERT(mJSContext); + + /* + * We treat the zone as being gray. We handle non-gray GCthings in the + * zone by not reporting their children to the CC. The black-gray invariant + * ensures that any JS children will also be non-gray, and thus don't need to be + * added to the graph. For C++ children, not representing the edge from the + * non-gray JS GCthings to the C++ object will keep the child alive. + * + * We don't allow zone merging in a WantAllTraces CC, because then these + * assumptions don't hold. + */ + aCb.DescribeGCedNode(false, "JS Zone"); + + /* + * Every JS child of everything in the zone is either in the zone + * or is a cross-compartment wrapper. In the former case, we don't need to + * represent these edges in the CC graph because JS objects are not ref counted. + * In the latter case, the JS engine keeps a map of these wrappers, which we + * iterate over. Edges between compartments in the same zone will add + * unnecessary loop edges to the graph (bug 842137). + */ + TraversalTracer trc(mJSContext, aCb); + js::VisitGrayWrapperTargets(aZone, NoteJSChildGrayWrapperShim, &trc); + + /* + * To find C++ children of things in the zone, we scan every JS Object in + * the zone. Only JS Objects can have C++ children. + */ + TraverseObjectShimClosure closure = { aCb, this }; + js::IterateGrayObjects(aZone, TraverseObjectShim, &closure); +} + +/* static */ void +CycleCollectedJSContext::TraverseObjectShim(void* aData, JS::GCCellPtr aThing) +{ + TraverseObjectShimClosure* closure = + static_cast<TraverseObjectShimClosure*>(aData); + + MOZ_ASSERT(aThing.is<JSObject>()); + closure->self->TraverseGCThing(CycleCollectedJSContext::TRAVERSE_CPP, + aThing, closure->cb); +} + +void +CycleCollectedJSContext::TraverseNativeRoots(nsCycleCollectionNoteRootCallback& aCb) +{ + // NB: This is here just to preserve the existing XPConnect order. I doubt it + // would hurt to do this after the JS holders. + TraverseAdditionalNativeRoots(aCb); + + for (auto iter = mJSHolders.Iter(); !iter.Done(); iter.Next()) { + void* holder = iter.Key(); + nsScriptObjectTracer*& tracer = iter.Data(); + + bool noteRoot = false; + if (MOZ_UNLIKELY(aCb.WantAllTraces())) { + noteRoot = true; + } else { + tracer->Trace(holder, + TraceCallbackFunc(CheckParticipatesInCycleCollection), + ¬eRoot); + } + + if (noteRoot) { + aCb.NoteNativeRoot(holder, tracer); + } + } +} + +/* static */ void +CycleCollectedJSContext::TraceBlackJS(JSTracer* aTracer, void* aData) +{ + CycleCollectedJSContext* self = static_cast<CycleCollectedJSContext*>(aData); + + self->TraceNativeBlackRoots(aTracer); +} + +/* static */ void +CycleCollectedJSContext::TraceGrayJS(JSTracer* aTracer, void* aData) +{ + CycleCollectedJSContext* self = static_cast<CycleCollectedJSContext*>(aData); + + // Mark these roots as gray so the CC can walk them later. + self->TraceNativeGrayRoots(aTracer); +} + +/* static */ void +CycleCollectedJSContext::GCCallback(JSContext* aContext, + JSGCStatus aStatus, + void* aData) +{ + CycleCollectedJSContext* self = static_cast<CycleCollectedJSContext*>(aData); + + MOZ_ASSERT(aContext == self->Context()); + + self->OnGC(aStatus); +} + +/* static */ void +CycleCollectedJSContext::GCSliceCallback(JSContext* aContext, + JS::GCProgress aProgress, + const JS::GCDescription& aDesc) +{ + CycleCollectedJSContext* self = CycleCollectedJSContext::Get(); + MOZ_ASSERT(self->Context() == aContext); + + if (aProgress == JS::GC_CYCLE_END) { + JS::gcreason::Reason reason = aDesc.reason_; + Unused << + NS_WARN_IF(NS_FAILED(DebuggerOnGCRunnable::Enqueue(aContext, aDesc)) && + reason != JS::gcreason::SHUTDOWN_CC && + reason != JS::gcreason::DESTROY_RUNTIME && + reason != JS::gcreason::XPCONNECT_SHUTDOWN); + } + + if (self->mPrevGCSliceCallback) { + self->mPrevGCSliceCallback(aContext, aProgress, aDesc); + } +} + +class MinorGCMarker : public TimelineMarker +{ +private: + JS::gcreason::Reason mReason; + +public: + MinorGCMarker(MarkerTracingType aTracingType, + JS::gcreason::Reason aReason) + : TimelineMarker("MinorGC", + aTracingType, + MarkerStackRequest::NO_STACK) + , mReason(aReason) + { + MOZ_ASSERT(aTracingType == MarkerTracingType::START || + aTracingType == MarkerTracingType::END); + } + + MinorGCMarker(JS::GCNurseryProgress aProgress, + JS::gcreason::Reason aReason) + : TimelineMarker("MinorGC", + aProgress == JS::GCNurseryProgress::GC_NURSERY_COLLECTION_START + ? MarkerTracingType::START + : MarkerTracingType::END, + MarkerStackRequest::NO_STACK) + , mReason(aReason) + { } + + virtual void + AddDetails(JSContext* aCx, + dom::ProfileTimelineMarker& aMarker) override + { + TimelineMarker::AddDetails(aCx, aMarker); + + if (GetTracingType() == MarkerTracingType::START) { + auto reason = JS::gcreason::ExplainReason(mReason); + aMarker.mCauseName.Construct(NS_ConvertUTF8toUTF16(reason)); + } + } + + virtual UniquePtr<AbstractTimelineMarker> + Clone() override + { + auto clone = MakeUnique<MinorGCMarker>(GetTracingType(), mReason); + clone->SetCustomTime(GetTime()); + return UniquePtr<AbstractTimelineMarker>(Move(clone)); + } +}; + +/* static */ void +CycleCollectedJSContext::GCNurseryCollectionCallback(JSContext* aContext, + JS::GCNurseryProgress aProgress, + JS::gcreason::Reason aReason) +{ + CycleCollectedJSContext* self = CycleCollectedJSContext::Get(); + MOZ_ASSERT(self->Context() == aContext); + MOZ_ASSERT(NS_IsMainThread()); + + RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get(); + if (timelines && !timelines->IsEmpty()) { + UniquePtr<AbstractTimelineMarker> abstractMarker( + MakeUnique<MinorGCMarker>(aProgress, aReason)); + timelines->AddMarkerForAllObservedDocShells(abstractMarker); + } + + if (self->mPrevGCNurseryCollectionCallback) { + self->mPrevGCNurseryCollectionCallback(aContext, aProgress, aReason); + } +} + + +/* static */ void +CycleCollectedJSContext::OutOfMemoryCallback(JSContext* aContext, + void* aData) +{ + CycleCollectedJSContext* self = static_cast<CycleCollectedJSContext*>(aData); + + MOZ_ASSERT(aContext == self->Context()); + + self->OnOutOfMemory(); +} + +/* static */ void +CycleCollectedJSContext::LargeAllocationFailureCallback(void* aData) +{ + CycleCollectedJSContext* self = static_cast<CycleCollectedJSContext*>(aData); + + self->OnLargeAllocationFailure(); +} + +class PromiseJobRunnable final : public Runnable +{ +public: + PromiseJobRunnable(JS::HandleObject aCallback, JS::HandleObject aAllocationSite, + nsIGlobalObject* aIncumbentGlobal) + : mCallback(new PromiseJobCallback(aCallback, aAllocationSite, aIncumbentGlobal)) + { + } + + virtual ~PromiseJobRunnable() + { + } + +protected: + NS_IMETHOD + Run() override + { + nsIGlobalObject* global = xpc::NativeGlobal(mCallback->CallbackPreserveColor()); + if (global && !global->IsDying()) { + mCallback->Call("promise callback"); + } + return NS_OK; + } + +private: + RefPtr<PromiseJobCallback> mCallback; +}; + +/* static */ +JSObject* +CycleCollectedJSContext::GetIncumbentGlobalCallback(JSContext* aCx) +{ + nsIGlobalObject* global = mozilla::dom::GetIncumbentGlobal(); + if (global) { + return global->GetGlobalJSObject(); + } + return nullptr; +} + +/* static */ +bool +CycleCollectedJSContext::EnqueuePromiseJobCallback(JSContext* aCx, + JS::HandleObject aJob, + JS::HandleObject aAllocationSite, + JS::HandleObject aIncumbentGlobal, + void* aData) +{ + CycleCollectedJSContext* self = static_cast<CycleCollectedJSContext*>(aData); + MOZ_ASSERT(aCx == self->Context()); + MOZ_ASSERT(Get() == self); + + nsIGlobalObject* global = nullptr; + if (aIncumbentGlobal) { + global = xpc::NativeGlobal(aIncumbentGlobal); + } + nsCOMPtr<nsIRunnable> runnable = new PromiseJobRunnable(aJob, aAllocationSite, global); + self->DispatchToMicroTask(runnable.forget()); + return true; +} + +#ifdef SPIDERMONKEY_PROMISE +/* static */ +void +CycleCollectedJSContext::PromiseRejectionTrackerCallback(JSContext* aCx, + JS::HandleObject aPromise, + PromiseRejectionHandlingState state, + void* aData) +{ +#ifdef DEBUG + CycleCollectedJSContext* self = static_cast<CycleCollectedJSContext*>(aData); +#endif // DEBUG + MOZ_ASSERT(aCx == self->Context()); + MOZ_ASSERT(Get() == self); + + if (state == PromiseRejectionHandlingState::Unhandled) { + PromiseDebugging::AddUncaughtRejection(aPromise); + } else { + PromiseDebugging::AddConsumedRejection(aPromise); + } +} +#endif // SPIDERMONKEY_PROMISE + +struct JsGcTracer : public TraceCallbacks +{ + virtual void Trace(JS::Heap<JS::Value>* aPtr, const char* aName, + void* aClosure) const override + { + JS::TraceEdge(static_cast<JSTracer*>(aClosure), aPtr, aName); + } + virtual void Trace(JS::Heap<jsid>* aPtr, const char* aName, + void* aClosure) const override + { + JS::TraceEdge(static_cast<JSTracer*>(aClosure), aPtr, aName); + } + virtual void Trace(JS::Heap<JSObject*>* aPtr, const char* aName, + void* aClosure) const override + { + JS::TraceEdge(static_cast<JSTracer*>(aClosure), aPtr, aName); + } + virtual void Trace(JSObject** aPtr, const char* aName, + void* aClosure) const override + { + js::UnsafeTraceManuallyBarrieredEdge(static_cast<JSTracer*>(aClosure), aPtr, aName); + } + virtual void Trace(JS::TenuredHeap<JSObject*>* aPtr, const char* aName, + void* aClosure) const override + { + JS::TraceEdge(static_cast<JSTracer*>(aClosure), aPtr, aName); + } + virtual void Trace(JS::Heap<JSString*>* aPtr, const char* aName, + void* aClosure) const override + { + JS::TraceEdge(static_cast<JSTracer*>(aClosure), aPtr, aName); + } + virtual void Trace(JS::Heap<JSScript*>* aPtr, const char* aName, + void* aClosure) const override + { + JS::TraceEdge(static_cast<JSTracer*>(aClosure), aPtr, aName); + } + virtual void Trace(JS::Heap<JSFunction*>* aPtr, const char* aName, + void* aClosure) const override + { + JS::TraceEdge(static_cast<JSTracer*>(aClosure), aPtr, aName); + } +}; + +void +mozilla::TraceScriptHolder(nsISupports* aHolder, JSTracer* aTracer) +{ + nsXPCOMCycleCollectionParticipant* participant = nullptr; + CallQueryInterface(aHolder, &participant); + participant->Trace(aHolder, JsGcTracer(), aTracer); +} + +void +CycleCollectedJSContext::TraceNativeGrayRoots(JSTracer* aTracer) +{ + MOZ_ASSERT(mJSContext); + + // NB: This is here just to preserve the existing XPConnect order. I doubt it + // would hurt to do this after the JS holders. + TraceAdditionalNativeGrayRoots(aTracer); + + for (auto iter = mJSHolders.Iter(); !iter.Done(); iter.Next()) { + void* holder = iter.Key(); + nsScriptObjectTracer*& tracer = iter.Data(); + tracer->Trace(holder, JsGcTracer(), aTracer); + } +} + +void +CycleCollectedJSContext::AddJSHolder(void* aHolder, nsScriptObjectTracer* aTracer) +{ + MOZ_ASSERT(mJSContext); + mJSHolders.Put(aHolder, aTracer); +} + +struct ClearJSHolder : public TraceCallbacks +{ + virtual void Trace(JS::Heap<JS::Value>* aPtr, const char*, void*) const override + { + aPtr->setUndefined(); + } + + virtual void Trace(JS::Heap<jsid>* aPtr, const char*, void*) const override + { + *aPtr = JSID_VOID; + } + + virtual void Trace(JS::Heap<JSObject*>* aPtr, const char*, void*) const override + { + *aPtr = nullptr; + } + + virtual void Trace(JSObject** aPtr, const char* aName, + void* aClosure) const override + { + *aPtr = nullptr; + } + + virtual void Trace(JS::TenuredHeap<JSObject*>* aPtr, const char*, void*) const override + { + *aPtr = nullptr; + } + + virtual void Trace(JS::Heap<JSString*>* aPtr, const char*, void*) const override + { + *aPtr = nullptr; + } + + virtual void Trace(JS::Heap<JSScript*>* aPtr, const char*, void*) const override + { + *aPtr = nullptr; + } + + virtual void Trace(JS::Heap<JSFunction*>* aPtr, const char*, void*) const override + { + *aPtr = nullptr; + } +}; + +void +CycleCollectedJSContext::RemoveJSHolder(void* aHolder) +{ + MOZ_ASSERT(mJSContext); + + nsScriptObjectTracer* tracer = mJSHolders.Get(aHolder); + if (!tracer) { + return; + } + tracer->Trace(aHolder, ClearJSHolder(), nullptr); + mJSHolders.Remove(aHolder); +} + +#ifdef DEBUG +bool +CycleCollectedJSContext::IsJSHolder(void* aHolder) +{ + MOZ_ASSERT(mJSContext); + return mJSHolders.Get(aHolder, nullptr); +} + +static void +AssertNoGcThing(JS::GCCellPtr aGCThing, const char* aName, void* aClosure) +{ + MOZ_ASSERT(!aGCThing); +} + +void +CycleCollectedJSContext::AssertNoObjectsToTrace(void* aPossibleJSHolder) +{ + MOZ_ASSERT(mJSContext); + + nsScriptObjectTracer* tracer = mJSHolders.Get(aPossibleJSHolder); + if (tracer) { + tracer->Trace(aPossibleJSHolder, TraceCallbackFunc(AssertNoGcThing), nullptr); + } +} +#endif + +already_AddRefed<nsIException> +CycleCollectedJSContext::GetPendingException() const +{ + MOZ_ASSERT(mJSContext); + + nsCOMPtr<nsIException> out = mPendingException; + return out.forget(); +} + +void +CycleCollectedJSContext::SetPendingException(nsIException* aException) +{ + MOZ_ASSERT(mJSContext); + mPendingException = aException; +} + +std::queue<nsCOMPtr<nsIRunnable>>& +CycleCollectedJSContext::GetPromiseMicroTaskQueue() +{ + MOZ_ASSERT(mJSContext); + return mPromiseMicroTaskQueue; +} + +std::queue<nsCOMPtr<nsIRunnable>>& +CycleCollectedJSContext::GetDebuggerPromiseMicroTaskQueue() +{ + MOZ_ASSERT(mJSContext); + return mDebuggerPromiseMicroTaskQueue; +} + +nsCycleCollectionParticipant* +CycleCollectedJSContext::GCThingParticipant() +{ + MOZ_ASSERT(mJSContext); + return &mGCThingCycleCollectorGlobal; +} + +nsCycleCollectionParticipant* +CycleCollectedJSContext::ZoneParticipant() +{ + MOZ_ASSERT(mJSContext); + return &mJSZoneCycleCollectorGlobal; +} + +nsresult +CycleCollectedJSContext::TraverseRoots(nsCycleCollectionNoteRootCallback& aCb) +{ + MOZ_ASSERT(mJSContext); + + TraverseNativeRoots(aCb); + + NoteWeakMapsTracer trc(mJSContext, aCb); + js::TraceWeakMaps(&trc); + + return NS_OK; +} + +bool +CycleCollectedJSContext::UsefulToMergeZones() const +{ + return false; +} + +void +CycleCollectedJSContext::FixWeakMappingGrayBits() const +{ + MOZ_ASSERT(mJSContext); + MOZ_ASSERT(!JS::IsIncrementalGCInProgress(mJSContext), + "Don't call FixWeakMappingGrayBits during a GC."); + FixWeakMappingGrayBitsTracer fixer(mJSContext); + fixer.FixAll(); +} + +bool +CycleCollectedJSContext::AreGCGrayBitsValid() const +{ + MOZ_ASSERT(mJSContext); + return js::AreGCGrayBitsValid(mJSContext); +} + +void +CycleCollectedJSContext::GarbageCollect(uint32_t aReason) const +{ + MOZ_ASSERT(mJSContext); + + MOZ_ASSERT(aReason < JS::gcreason::NUM_REASONS); + JS::gcreason::Reason gcreason = static_cast<JS::gcreason::Reason>(aReason); + + JS::PrepareForFullGC(mJSContext); + JS::GCForReason(mJSContext, GC_NORMAL, gcreason); +} + +void +CycleCollectedJSContext::JSObjectsTenured() +{ + MOZ_ASSERT(mJSContext); + + for (auto iter = mNurseryObjects.Iter(); !iter.Done(); iter.Next()) { + nsWrapperCache* cache = iter.Get(); + JSObject* wrapper = cache->GetWrapperPreserveColor(); + MOZ_ASSERT(wrapper); + if (!JS::ObjectIsTenured(wrapper)) { + MOZ_ASSERT(!cache->PreservingWrapper()); + const JSClass* jsClass = js::GetObjectJSClass(wrapper); + jsClass->doFinalize(nullptr, wrapper); + } + } + +#ifdef DEBUG +for (auto iter = mPreservedNurseryObjects.Iter(); !iter.Done(); iter.Next()) { + MOZ_ASSERT(JS::ObjectIsTenured(iter.Get().get())); +} +#endif + + mNurseryObjects.Clear(); + mPreservedNurseryObjects.Clear(); +} + +void +CycleCollectedJSContext::NurseryWrapperAdded(nsWrapperCache* aCache) +{ + MOZ_ASSERT(mJSContext); + MOZ_ASSERT(aCache); + MOZ_ASSERT(aCache->GetWrapperPreserveColor()); + MOZ_ASSERT(!JS::ObjectIsTenured(aCache->GetWrapperPreserveColor())); + mNurseryObjects.InfallibleAppend(aCache); +} + +void +CycleCollectedJSContext::NurseryWrapperPreserved(JSObject* aWrapper) +{ + MOZ_ASSERT(mJSContext); + + mPreservedNurseryObjects.InfallibleAppend( + JS::PersistentRooted<JSObject*>(mJSContext, aWrapper)); +} + +void +CycleCollectedJSContext::DeferredFinalize(DeferredFinalizeAppendFunction aAppendFunc, + DeferredFinalizeFunction aFunc, + void* aThing) +{ + MOZ_ASSERT(mJSContext); + + void* thingArray = nullptr; + bool hadThingArray = mDeferredFinalizerTable.Get(aFunc, &thingArray); + + thingArray = aAppendFunc(thingArray, aThing); + if (!hadThingArray) { + mDeferredFinalizerTable.Put(aFunc, thingArray); + } +} + +void +CycleCollectedJSContext::DeferredFinalize(nsISupports* aSupports) +{ + MOZ_ASSERT(mJSContext); + + typedef DeferredFinalizerImpl<nsISupports> Impl; + DeferredFinalize(Impl::AppendDeferredFinalizePointer, Impl::DeferredFinalize, + aSupports); +} + +void +CycleCollectedJSContext::DumpJSHeap(FILE* aFile) +{ + js::DumpHeap(Context(), aFile, js::CollectNurseryBeforeDump); +} + +void +CycleCollectedJSContext::ProcessStableStateQueue() +{ + MOZ_ASSERT(mJSContext); + MOZ_RELEASE_ASSERT(!mDoingStableStates); + mDoingStableStates = true; + + for (uint32_t i = 0; i < mStableStateEvents.Length(); ++i) { + nsCOMPtr<nsIRunnable> event = mStableStateEvents[i].forget(); + event->Run(); + } + + mStableStateEvents.Clear(); + mDoingStableStates = false; +} + +void +CycleCollectedJSContext::ProcessMetastableStateQueue(uint32_t aRecursionDepth) +{ + MOZ_ASSERT(mJSContext); + MOZ_RELEASE_ASSERT(!mDoingStableStates); + mDoingStableStates = true; + + nsTArray<RunInMetastableStateData> localQueue = Move(mMetastableStateEvents); + + for (uint32_t i = 0; i < localQueue.Length(); ++i) + { + RunInMetastableStateData& data = localQueue[i]; + if (data.mRecursionDepth != aRecursionDepth) { + continue; + } + + { + nsCOMPtr<nsIRunnable> runnable = data.mRunnable.forget(); + runnable->Run(); + } + + localQueue.RemoveElementAt(i--); + } + + // If the queue has events in it now, they were added from something we called, + // so they belong at the end of the queue. + localQueue.AppendElements(mMetastableStateEvents); + localQueue.SwapElements(mMetastableStateEvents); + mDoingStableStates = false; +} + +void +CycleCollectedJSContext::AfterProcessTask(uint32_t aRecursionDepth) +{ + MOZ_ASSERT(mJSContext); + + // See HTML 6.1.4.2 Processing model + + // Execute any events that were waiting for a microtask to complete. + // This is not (yet) in the spec. + ProcessMetastableStateQueue(aRecursionDepth); + + // Step 4.1: Execute microtasks. + if (!mDisableMicroTaskCheckpoint) { + if (NS_IsMainThread()) { + nsContentUtils::PerformMainThreadMicroTaskCheckpoint(); + Promise::PerformMicroTaskCheckpoint(); + } else { + Promise::PerformWorkerMicroTaskCheckpoint(); + } + } + + // Step 4.2 Execute any events that were waiting for a stable state. + ProcessStableStateQueue(); +} + +void +CycleCollectedJSContext::AfterProcessMicrotask() +{ + MOZ_ASSERT(mJSContext); + AfterProcessMicrotask(RecursionDepth()); +} + +void +CycleCollectedJSContext::AfterProcessMicrotask(uint32_t aRecursionDepth) +{ + MOZ_ASSERT(mJSContext); + + // Between microtasks, execute any events that were waiting for a microtask + // to complete. + ProcessMetastableStateQueue(aRecursionDepth); +} + +uint32_t +CycleCollectedJSContext::RecursionDepth() +{ + return mOwningThread->RecursionDepth(); +} + +void +CycleCollectedJSContext::RunInStableState(already_AddRefed<nsIRunnable>&& aRunnable) +{ + MOZ_ASSERT(mJSContext); + mStableStateEvents.AppendElement(Move(aRunnable)); +} + +void +CycleCollectedJSContext::RunInMetastableState(already_AddRefed<nsIRunnable>&& aRunnable) +{ + MOZ_ASSERT(mJSContext); + + RunInMetastableStateData data; + data.mRunnable = aRunnable; + + MOZ_ASSERT(mOwningThread); + data.mRecursionDepth = RecursionDepth(); + + // There must be an event running to get here. +#ifndef MOZ_WIDGET_COCOA + MOZ_ASSERT(data.mRecursionDepth > mBaseRecursionDepth); +#else + // XXX bug 1261143 + // Recursion depth should be greater than mBaseRecursionDepth, + // or the runnable will stay in the queue forever. + if (data.mRecursionDepth <= mBaseRecursionDepth) { + data.mRecursionDepth = mBaseRecursionDepth + 1; + } +#endif + + mMetastableStateEvents.AppendElement(Move(data)); +} + +IncrementalFinalizeRunnable::IncrementalFinalizeRunnable(CycleCollectedJSContext* aCx, + DeferredFinalizerTable& aFinalizers) + : mContext(aCx) + , mFinalizeFunctionToRun(0) + , mReleasing(false) +{ + for (auto iter = aFinalizers.Iter(); !iter.Done(); iter.Next()) { + DeferredFinalizeFunction& function = iter.Key(); + void*& data = iter.Data(); + + DeferredFinalizeFunctionHolder* holder = + mDeferredFinalizeFunctions.AppendElement(); + holder->run = function; + holder->data = data; + + iter.Remove(); + } +} + +IncrementalFinalizeRunnable::~IncrementalFinalizeRunnable() +{ + MOZ_ASSERT(this != mContext->mFinalizeRunnable); +} + +void +IncrementalFinalizeRunnable::ReleaseNow(bool aLimited) +{ + if (mReleasing) { + NS_WARNING("Re-entering ReleaseNow"); + return; + } + { + mozilla::AutoRestore<bool> ar(mReleasing); + mReleasing = true; + MOZ_ASSERT(mDeferredFinalizeFunctions.Length() != 0, + "We should have at least ReleaseSliceNow to run"); + MOZ_ASSERT(mFinalizeFunctionToRun < mDeferredFinalizeFunctions.Length(), + "No more finalizers to run?"); + + TimeDuration sliceTime = TimeDuration::FromMilliseconds(SliceMillis); + TimeStamp started = TimeStamp::Now(); + bool timeout = false; + do { + const DeferredFinalizeFunctionHolder& function = + mDeferredFinalizeFunctions[mFinalizeFunctionToRun]; + if (aLimited) { + bool done = false; + while (!timeout && !done) { + /* + * We don't want to read the clock too often, so we try to + * release slices of 100 items. + */ + done = function.run(100, function.data); + timeout = TimeStamp::Now() - started >= sliceTime; + } + if (done) { + ++mFinalizeFunctionToRun; + } + if (timeout) { + break; + } + } else { + while (!function.run(UINT32_MAX, function.data)); + ++mFinalizeFunctionToRun; + } + } while (mFinalizeFunctionToRun < mDeferredFinalizeFunctions.Length()); + } + + if (mFinalizeFunctionToRun == mDeferredFinalizeFunctions.Length()) { + MOZ_ASSERT(mContext->mFinalizeRunnable == this); + mDeferredFinalizeFunctions.Clear(); + // NB: This may delete this! + mContext->mFinalizeRunnable = nullptr; + } +} + +NS_IMETHODIMP +IncrementalFinalizeRunnable::Run() +{ + if (mContext->mFinalizeRunnable != this) { + /* These items were already processed synchronously in JSGC_END. */ + MOZ_ASSERT(!mDeferredFinalizeFunctions.Length()); + return NS_OK; + } + + TimeStamp start = TimeStamp::Now(); + ReleaseNow(true); + + if (mDeferredFinalizeFunctions.Length()) { + nsresult rv = NS_DispatchToCurrentThread(this); + if (NS_FAILED(rv)) { + ReleaseNow(false); + } + } + + uint32_t duration = (uint32_t)((TimeStamp::Now() - start).ToMilliseconds()); + Telemetry::Accumulate(Telemetry::DEFERRED_FINALIZE_ASYNC, duration); + + return NS_OK; +} + +void +CycleCollectedJSContext::FinalizeDeferredThings(DeferredFinalizeType aType) +{ + MOZ_ASSERT(mJSContext); + + /* + * If the previous GC created a runnable to finalize objects + * incrementally, and if it hasn't finished yet, finish it now. We + * don't want these to build up. We also don't want to allow any + * existing incremental finalize runnables to run after a + * non-incremental GC, since they are often used to detect leaks. + */ + if (mFinalizeRunnable) { + mFinalizeRunnable->ReleaseNow(false); + if (mFinalizeRunnable) { + // If we re-entered ReleaseNow, we couldn't delete mFinalizeRunnable and + // we need to just continue processing it. + return; + } + } + + if (mDeferredFinalizerTable.Count() == 0) { + return; + } + + mFinalizeRunnable = new IncrementalFinalizeRunnable(this, + mDeferredFinalizerTable); + + // Everything should be gone now. + MOZ_ASSERT(mDeferredFinalizerTable.Count() == 0); + + if (aType == FinalizeIncrementally) { + NS_DispatchToCurrentThread(mFinalizeRunnable); + } else { + mFinalizeRunnable->ReleaseNow(false); + MOZ_ASSERT(!mFinalizeRunnable); + } +} + +void +CycleCollectedJSContext::AnnotateAndSetOutOfMemory(OOMState* aStatePtr, + OOMState aNewState) +{ + MOZ_ASSERT(mJSContext); + + *aStatePtr = aNewState; +#ifdef MOZ_CRASHREPORTER + CrashReporter::AnnotateCrashReport(aStatePtr == &mOutOfMemoryState + ? NS_LITERAL_CSTRING("JSOutOfMemory") + : NS_LITERAL_CSTRING("JSLargeAllocationFailure"), + aNewState == OOMState::Reporting + ? NS_LITERAL_CSTRING("Reporting") + : aNewState == OOMState::Reported + ? NS_LITERAL_CSTRING("Reported") + : NS_LITERAL_CSTRING("Recovered")); +#endif +} + +void +CycleCollectedJSContext::OnGC(JSGCStatus aStatus) +{ + MOZ_ASSERT(mJSContext); + + switch (aStatus) { + case JSGC_BEGIN: + nsCycleCollector_prepareForGarbageCollection(); + mZonesWaitingForGC.Clear(); + break; + case JSGC_END: { +#ifdef MOZ_CRASHREPORTER + if (mOutOfMemoryState == OOMState::Reported) { + AnnotateAndSetOutOfMemory(&mOutOfMemoryState, OOMState::Recovered); + } + if (mLargeAllocationFailureState == OOMState::Reported) { + AnnotateAndSetOutOfMemory(&mLargeAllocationFailureState, OOMState::Recovered); + } +#endif + + // Do any deferred finalization of native objects. + FinalizeDeferredThings(JS::WasIncrementalGC(mJSContext) ? FinalizeIncrementally : + FinalizeNow); + break; + } + default: + MOZ_CRASH(); + } + + CustomGCCallback(aStatus); +} + +void +CycleCollectedJSContext::OnOutOfMemory() +{ + MOZ_ASSERT(mJSContext); + + AnnotateAndSetOutOfMemory(&mOutOfMemoryState, OOMState::Reporting); + CustomOutOfMemoryCallback(); + AnnotateAndSetOutOfMemory(&mOutOfMemoryState, OOMState::Reported); +} + +void +CycleCollectedJSContext::OnLargeAllocationFailure() +{ + MOZ_ASSERT(mJSContext); + + AnnotateAndSetOutOfMemory(&mLargeAllocationFailureState, OOMState::Reporting); + CustomLargeAllocationFailureCallback(); + AnnotateAndSetOutOfMemory(&mLargeAllocationFailureState, OOMState::Reported); +} + +void +CycleCollectedJSContext::PrepareWaitingZonesForGC() +{ + if (mZonesWaitingForGC.Count() == 0) { + JS::PrepareForFullGC(Context()); + } else { + for (auto iter = mZonesWaitingForGC.Iter(); !iter.Done(); iter.Next()) { + JS::PrepareZoneForGC(iter.Get()->GetKey()); + } + mZonesWaitingForGC.Clear(); + } +} + +void +CycleCollectedJSContext::DispatchToMicroTask(already_AddRefed<nsIRunnable> aRunnable) +{ + RefPtr<nsIRunnable> runnable(aRunnable); + + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(runnable); + + mPromiseMicroTaskQueue.push(runnable.forget()); +} + +void +CycleCollectedJSContext::EnvironmentPreparer::invoke(JS::HandleObject scope, + js::ScriptEnvironmentPreparer::Closure& closure) +{ + nsIGlobalObject* global = xpc::NativeGlobal(scope); + + // Not much we can do if we simply don't have a usable global here... + NS_ENSURE_TRUE_VOID(global && global->GetGlobalJSObject()); + + AutoEntryScript aes(global, "JS-engine-initiated execution"); + + MOZ_ASSERT(!JS_IsExceptionPending(aes.cx())); + + DebugOnly<bool> ok = closure(aes.cx()); + + MOZ_ASSERT_IF(ok, !JS_IsExceptionPending(aes.cx())); + + // The AutoEntryScript will check for JS_IsExceptionPending on the + // JSContext and report it as needed as it comes off the stack. +} |