1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 // We're dividing JS objects into 3 categories:
9 // 1. "real" roots, held by the JS engine itself or rooted through the root
10 // and lock JS APIs. Roots from this category are considered black in the
11 // cycle collector, any cycle they participate in is uncollectable.
13 // 2. certain roots held by C++ objects that are guaranteed to be alive.
14 // Roots from this category are considered black in the cycle collector,
15 // and any cycle they participate in is uncollectable. These roots are
16 // traced from TraceNativeBlackRoots.
18 // 3. all other roots held by C++ objects that participate in cycle collection,
19 // held by us (see TraceNativeGrayRoots). Roots from this category are
20 // considered grey in the cycle collector; whether or not they are collected
21 // depends on the objects that hold them.
23 // Note that if a root is in multiple categories the fact that it is in
24 // category 1 or 2 that takes precedence, so it will be considered black.
26 // During garbage collection we switch to an additional mark color (gray) when
27 // tracing inside TraceNativeGrayRoots. This allows us to walk those roots later
28 // on and add all objects reachable only from them to the cycle collector.
32 // 1. marking of the roots in category 1 by having the JS GC do its marking
33 // 2. marking of the roots in category 2 by having the JS GC call us back
34 // (via JS_SetExtraGCRootsTracer) and running TraceNativeBlackRoots
35 // 3. marking of the roots in category 3 by
36 // TraceNativeGrayRootsInCollectingZones using an additional color (gray).
37 // 4. end of GC, GC can sweep its heap
39 // At some later point, when the cycle collector runs:
41 // 5. walk gray objects and add them to the cycle collector, cycle collect
43 // JS objects that are part of cycles the cycle collector breaks will be
44 // collected by the next JS GC.
46 // If WantAllTraces() is false the cycle collector will not traverse roots
47 // from category 1 or any JS objects held by them. Any JS objects they hold
48 // will already be marked by the JS GC and will thus be colored black
49 // themselves. Any C++ objects they hold will have a missing (untraversed)
50 // edge from the JS object to the C++ object and so it will be marked black
51 // too. This decreases the number of objects that the cycle collector has to
53 // To improve debugging, if WantAllTraces() is true all JS objects are
56 #include "mozilla/CycleCollectedJSRuntime.h"
62 #include "js/friend/DumpFunctions.h" // js::DumpHeap
64 #include "js/HeapAPI.h"
65 #include "js/Object.h" // JS::GetClass, JS::GetCompartment, JS::GetPrivate
66 #include "js/Warnings.h" // JS::SetWarningReporter
67 #include "jsfriendapi.h"
68 #include "mozilla/ArrayUtils.h"
69 #include "mozilla/AutoRestore.h"
70 #include "mozilla/CycleCollectedJSContext.h"
71 #include "mozilla/DebuggerOnGCRunnable.h"
72 #include "mozilla/MemoryReporting.h"
73 #include "mozilla/ProfilerLabels.h"
74 #include "mozilla/ProfilerMarkers.h"
75 #include "mozilla/Sprintf.h"
76 #include "mozilla/Telemetry.h"
77 #include "mozilla/TimelineConsumers.h"
78 #include "mozilla/TimelineMarker.h"
79 #include "mozilla/Unused.h"
80 #include "mozilla/dom/DOMJSClass.h"
81 #include "mozilla/dom/JSExecutionManager.h"
82 #include "mozilla/dom/ProfileTimelineMarkerBinding.h"
83 #include "mozilla/dom/Promise.h"
84 #include "mozilla/dom/PromiseBinding.h"
85 #include "mozilla/dom/PromiseDebugging.h"
86 #include "mozilla/dom/ScriptSettings.h"
87 #include "nsContentUtils.h"
88 #include "nsCycleCollectionNoteRootCallback.h"
89 #include "nsCycleCollectionParticipant.h"
90 #include "nsCycleCollector.h"
91 #include "nsDOMJSUtils.h"
92 #include "nsExceptionHandler.h"
93 #include "nsJSUtils.h"
94 #include "nsStringBuffer.h"
95 #include "nsWrapperCache.h"
97 #if defined(XP_MACOSX)
98 # include "nsMacUtilsImpl.h"
101 #include "nsThread.h"
102 #include "nsThreadUtils.h"
103 #include "xpcpublic.h"
106 // For performance reasons, we make the JS Dev Error Interceptor a Nightly-only
108 # define MOZ_JS_DEV_ERROR_INTERCEPTOR = 1
109 #endif // NIGHTLY_BUILD
111 using namespace mozilla
;
112 using namespace mozilla::dom
;
116 struct DeferredFinalizeFunctionHolder
{
117 DeferredFinalizeFunction run
;
121 class IncrementalFinalizeRunnable
: public DiscardableRunnable
{
122 typedef AutoTArray
<DeferredFinalizeFunctionHolder
, 16> DeferredFinalizeArray
;
123 typedef CycleCollectedJSRuntime::DeferredFinalizerTable
124 DeferredFinalizerTable
;
126 CycleCollectedJSRuntime
* mRuntime
;
127 DeferredFinalizeArray mDeferredFinalizeFunctions
;
128 uint32_t mFinalizeFunctionToRun
;
131 static const PRTime SliceMillis
= 5; /* ms */
134 IncrementalFinalizeRunnable(CycleCollectedJSRuntime
* aRt
,
135 DeferredFinalizerTable
& aFinalizerTable
);
136 virtual ~IncrementalFinalizeRunnable();
138 void ReleaseNow(bool aLimited
);
143 } // namespace mozilla
145 struct NoteWeakMapChildrenTracer
: public JS::CallbackTracer
{
146 NoteWeakMapChildrenTracer(JSRuntime
* aRt
,
147 nsCycleCollectionNoteRootCallback
& aCb
)
148 : JS::CallbackTracer(aRt
, JS::TracerKind::Callback
,
149 JS::IdTraceAction::CanSkip
),
154 mKeyDelegate(nullptr) {}
155 void onChild(const JS::GCCellPtr
& aThing
) override
;
156 nsCycleCollectionNoteRootCallback
& mCb
;
160 JSObject
* mKeyDelegate
;
163 void NoteWeakMapChildrenTracer::onChild(const JS::GCCellPtr
& aThing
) {
164 if (aThing
.is
<JSString
>()) {
168 if (!JS::GCThingIsMarkedGray(aThing
) && !mCb
.WantAllTraces()) {
172 if (JS::IsCCTraceKind(aThing
.kind())) {
173 mCb
.NoteWeakMapping(mMap
, mKey
, mKeyDelegate
, aThing
);
176 JS::TraceChildren(this, aThing
);
180 struct NoteWeakMapsTracer
: public js::WeakMapTracer
{
181 NoteWeakMapsTracer(JSRuntime
* aRt
, nsCycleCollectionNoteRootCallback
& aCccb
)
182 : js::WeakMapTracer(aRt
), mCb(aCccb
), mChildTracer(aRt
, aCccb
) {}
183 void trace(JSObject
* aMap
, JS::GCCellPtr aKey
, JS::GCCellPtr aValue
) override
;
184 nsCycleCollectionNoteRootCallback
& mCb
;
185 NoteWeakMapChildrenTracer mChildTracer
;
188 void NoteWeakMapsTracer::trace(JSObject
* aMap
, JS::GCCellPtr aKey
,
189 JS::GCCellPtr aValue
) {
190 // If nothing that could be held alive by this entry is marked gray, return.
191 if ((!aKey
|| !JS::GCThingIsMarkedGray(aKey
)) &&
192 MOZ_LIKELY(!mCb
.WantAllTraces())) {
193 if (!aValue
|| !JS::GCThingIsMarkedGray(aValue
) || aValue
.is
<JSString
>()) {
198 // The cycle collector can only properly reason about weak maps if it can
199 // reason about the liveness of their keys, which in turn requires that
200 // the key can be represented in the cycle collector graph. All existing
201 // uses of weak maps use either objects or scripts as keys, which are okay.
202 MOZ_ASSERT(JS::IsCCTraceKind(aKey
.kind()));
204 // As an emergency fallback for non-debug builds, if the key is not
205 // representable in the cycle collector graph, we treat it as marked. This
206 // can cause leaks, but is preferable to ignoring the binding, which could
207 // cause the cycle collector to free live objects.
208 if (!JS::IsCCTraceKind(aKey
.kind())) {
212 JSObject
* kdelegate
= nullptr;
213 if (aKey
.is
<JSObject
>()) {
214 kdelegate
= js::UncheckedUnwrapWithoutExpose(&aKey
.as
<JSObject
>());
217 if (JS::IsCCTraceKind(aValue
.kind())) {
218 mCb
.NoteWeakMapping(aMap
, aKey
, kdelegate
, aValue
);
220 mChildTracer
.mTracedAny
= false;
221 mChildTracer
.mMap
= aMap
;
222 mChildTracer
.mKey
= aKey
;
223 mChildTracer
.mKeyDelegate
= kdelegate
;
225 if (!aValue
.is
<JSString
>()) {
226 JS::TraceChildren(&mChildTracer
, aValue
);
229 // The delegate could hold alive the key, so report something to the CC
230 // if we haven't already.
231 if (!mChildTracer
.mTracedAny
&& aKey
&& JS::GCThingIsMarkedGray(aKey
) &&
233 mCb
.NoteWeakMapping(aMap
, aKey
, kdelegate
, nullptr);
238 // Report whether the key or value of a weak mapping entry are gray but need to
240 static void ShouldWeakMappingEntryBeBlack(JSObject
* aMap
, JS::GCCellPtr aKey
,
241 JS::GCCellPtr aValue
,
242 bool* aKeyShouldBeBlack
,
243 bool* aValueShouldBeBlack
) {
244 *aKeyShouldBeBlack
= false;
245 *aValueShouldBeBlack
= false;
247 // If nothing that could be held alive by this entry is marked gray, return.
248 bool keyMightNeedMarking
= aKey
&& JS::GCThingIsMarkedGray(aKey
);
249 bool valueMightNeedMarking
= aValue
&& JS::GCThingIsMarkedGray(aValue
) &&
250 aValue
.kind() != JS::TraceKind::String
;
251 if (!keyMightNeedMarking
&& !valueMightNeedMarking
) {
255 if (!JS::IsCCTraceKind(aKey
.kind())) {
259 if (keyMightNeedMarking
&& aKey
.is
<JSObject
>()) {
260 JSObject
* kdelegate
=
261 js::UncheckedUnwrapWithoutExpose(&aKey
.as
<JSObject
>());
262 if (kdelegate
&& !JS::ObjectIsMarkedGray(kdelegate
) &&
263 (!aMap
|| !JS::ObjectIsMarkedGray(aMap
))) {
264 *aKeyShouldBeBlack
= true;
268 if (aValue
&& JS::GCThingIsMarkedGray(aValue
) &&
269 (!aKey
|| !JS::GCThingIsMarkedGray(aKey
)) &&
270 (!aMap
|| !JS::ObjectIsMarkedGray(aMap
)) &&
271 aValue
.kind() != JS::TraceKind::Shape
) {
272 *aValueShouldBeBlack
= true;
276 struct FixWeakMappingGrayBitsTracer
: public js::WeakMapTracer
{
277 explicit FixWeakMappingGrayBitsTracer(JSRuntime
* aRt
)
278 : js::WeakMapTracer(aRt
) {}
283 js::TraceWeakMaps(this);
284 } while (mAnyMarked
);
287 void trace(JSObject
* aMap
, JS::GCCellPtr aKey
,
288 JS::GCCellPtr aValue
) override
{
289 bool keyShouldBeBlack
;
290 bool valueShouldBeBlack
;
291 ShouldWeakMappingEntryBeBlack(aMap
, aKey
, aValue
, &keyShouldBeBlack
,
292 &valueShouldBeBlack
);
293 if (keyShouldBeBlack
&& JS::UnmarkGrayGCThingRecursively(aKey
)) {
297 if (valueShouldBeBlack
&& JS::UnmarkGrayGCThingRecursively(aValue
)) {
302 MOZ_INIT_OUTSIDE_CTOR
bool mAnyMarked
;
306 // Check whether weak maps are marked correctly according to the logic above.
307 struct CheckWeakMappingGrayBitsTracer
: public js::WeakMapTracer
{
308 explicit CheckWeakMappingGrayBitsTracer(JSRuntime
* aRt
)
309 : js::WeakMapTracer(aRt
), mFailed(false) {}
311 static bool Check(JSRuntime
* aRt
) {
312 CheckWeakMappingGrayBitsTracer
tracer(aRt
);
313 js::TraceWeakMaps(&tracer
);
314 return !tracer
.mFailed
;
317 void trace(JSObject
* aMap
, JS::GCCellPtr aKey
,
318 JS::GCCellPtr aValue
) override
{
319 bool keyShouldBeBlack
;
320 bool valueShouldBeBlack
;
321 ShouldWeakMappingEntryBeBlack(aMap
, aKey
, aValue
, &keyShouldBeBlack
,
322 &valueShouldBeBlack
);
324 if (keyShouldBeBlack
) {
325 fprintf(stderr
, "Weak mapping key %p of map %p should be black\n",
326 aKey
.asCell(), aMap
);
330 if (valueShouldBeBlack
) {
331 fprintf(stderr
, "Weak mapping value %p of map %p should be black\n",
332 aValue
.asCell(), aMap
);
341 static void CheckParticipatesInCycleCollection(JS::GCCellPtr aThing
,
344 bool* cycleCollectionEnabled
= static_cast<bool*>(aClosure
);
346 if (*cycleCollectionEnabled
) {
350 if (JS::IsCCTraceKind(aThing
.kind()) && JS::GCThingIsMarkedGray(aThing
)) {
351 *cycleCollectionEnabled
= true;
356 JSGCThingParticipant::TraverseNative(void* aPtr
,
357 nsCycleCollectionTraversalCallback
& aCb
) {
358 auto runtime
= reinterpret_cast<CycleCollectedJSRuntime
*>(
359 reinterpret_cast<char*>(this) -
360 offsetof(CycleCollectedJSRuntime
, mGCThingCycleCollectorGlobal
));
362 JS::GCCellPtr
cellPtr(aPtr
, JS::GCThingTraceKind(aPtr
));
363 runtime
->TraverseGCThing(CycleCollectedJSRuntime::TRAVERSE_FULL
, cellPtr
,
368 // NB: This is only used to initialize the participant in
369 // CycleCollectedJSRuntime. It should never be used directly.
370 static JSGCThingParticipant sGCThingCycleCollectorGlobal
;
373 JSZoneParticipant::TraverseNative(void* aPtr
,
374 nsCycleCollectionTraversalCallback
& aCb
) {
375 auto runtime
= reinterpret_cast<CycleCollectedJSRuntime
*>(
376 reinterpret_cast<char*>(this) -
377 offsetof(CycleCollectedJSRuntime
, mJSZoneCycleCollectorGlobal
));
379 MOZ_ASSERT(!aCb
.WantAllTraces());
380 JS::Zone
* zone
= static_cast<JS::Zone
*>(aPtr
);
382 runtime
->TraverseZone(zone
, aCb
);
386 struct TraversalTracer
: public JS::CallbackTracer
{
387 TraversalTracer(JSRuntime
* aRt
, nsCycleCollectionTraversalCallback
& aCb
)
388 : JS::CallbackTracer(aRt
, JS::TracerKind::Callback
,
389 JS::TraceOptions(JS::WeakMapTraceAction::Skip
,
390 JS::WeakEdgeTraceAction::Trace
,
391 JS::IdTraceAction::CanSkip
)),
393 void onChild(const JS::GCCellPtr
& aThing
) override
;
394 nsCycleCollectionTraversalCallback
& mCb
;
397 void TraversalTracer::onChild(const JS::GCCellPtr
& aThing
) {
398 // Allow re-use of this tracer inside trace callback.
399 JS::AutoClearTracingContext
actc(this);
401 // Checking strings and symbols for being gray is rather slow, and we don't
402 // need either of them for the cycle collector.
403 if (aThing
.is
<JSString
>() || aThing
.is
<JS::Symbol
>()) {
407 // Don't traverse non-gray objects, unless we want all traces.
408 if (!JS::GCThingIsMarkedGray(aThing
) && !mCb
.WantAllTraces()) {
413 * This function needs to be careful to avoid stack overflow. Normally, when
414 * IsCCTraceKind is true, the recursion terminates immediately as we just add
415 * |thing| to the CC graph. So overflow is only possible when there are long
416 * or cyclic chains of non-IsCCTraceKind GC things. Places where this can
417 * occur use special APIs to handle such chains iteratively.
419 if (JS::IsCCTraceKind(aThing
.kind())) {
420 if (MOZ_UNLIKELY(mCb
.WantDebugInfo())) {
422 context().getEdgeName(buffer
, sizeof(buffer
));
423 mCb
.NoteNextEdgeName(buffer
);
425 mCb
.NoteJSChild(aThing
);
426 } else if (aThing
.is
<js::Shape
>()) {
427 // The maximum depth of traversal when tracing a Shape is unbounded, due to
428 // the parent pointers on the shape.
429 JS_TraceShapeCycleCollectorChildren(this, aThing
);
430 } else if (aThing
.is
<js::ObjectGroup
>()) {
431 // The maximum depth of traversal when tracing an ObjectGroup is unbounded,
432 // due to information attached to the groups which can lead other groups to
434 JS_TraceObjectGroupCycleCollectorChildren(this, aThing
);
436 JS::TraceChildren(this, aThing
);
441 * The cycle collection participant for a Zone is intended to produce the same
442 * results as if all of the gray GCthings in a zone were merged into a single
443 * node, except for self-edges. This avoids the overhead of representing all of
444 * the GCthings in the zone in the cycle collector graph, which should be much
445 * faster if many of the GCthings in the zone are gray.
447 * Zone merging should not always be used, because it is a conservative
448 * approximation of the true cycle collector graph that can incorrectly identify
449 * some garbage objects as being live. For instance, consider two cycles that
450 * pass through a zone, where one is garbage and the other is live. If we merge
451 * the entire zone, the cycle collector will think that both are alive.
453 * We don't have to worry about losing track of a garbage cycle, because any
454 * such garbage cycle incorrectly identified as live must contain at least one
455 * C++ to JS edge, and XPConnect will always add the C++ object to the CC graph.
456 * (This is in contrast to pure C++ garbage cycles, which must always be
457 * properly identified, because we clear the purple buffer during every CC,
458 * which may contain the last reference to a garbage cycle.)
461 // NB: This is only used to initialize the participant in
462 // CycleCollectedJSRuntime. It should never be used directly.
463 static const JSZoneParticipant sJSZoneCycleCollectorGlobal
;
465 static void JSObjectsTenuredCb(JSContext
* aContext
, void* aData
) {
466 static_cast<CycleCollectedJSRuntime
*>(aData
)->JSObjectsTenured();
469 static void MozCrashWarningReporter(JSContext
*, JSErrorReport
*) {
470 MOZ_CRASH("Why is someone touching JSAPI without an AutoJSAPI?");
473 JSHolderMap::Entry::Entry() : Entry(nullptr, nullptr, nullptr) {}
475 JSHolderMap::Entry::Entry(void* aHolder
, nsScriptObjectTracer
* aTracer
,
486 JSHolderMap::JSHolderMap() : mJSHolderMap(256) {}
488 template <typename F
>
489 inline void JSHolderMap::ForEach(F
&& f
, WhichHolders aWhich
) {
490 // Multi-zone JS holders must always be considered.
491 ForEach(mAnyZoneJSHolders
, f
, nullptr);
493 for (auto i
= mPerZoneJSHolders
.modIter(); !i
.done(); i
.next()) {
494 if (aWhich
== HoldersInCollectingZones
&&
495 !JS::ZoneIsCollecting(i
.get().key())) {
499 EntryVector
* holders
= i
.get().value().get();
500 ForEach(*holders
, f
, i
.get().key());
501 if (holders
->IsEmpty()) {
507 template <typename F
>
508 inline void JSHolderMap::ForEach(EntryVector
& aJSHolders
, const F
& f
,
510 for (auto iter
= aJSHolders
.Iter(); !iter
.Done(); iter
.Next()) {
511 Entry
* entry
= &iter
.Get();
513 // If the entry has been cleared, remove it and shrink the vector.
514 if (!entry
->mHolder
&& !RemoveEntry(aJSHolders
, entry
)) {
515 break; // Removed the last entry.
518 MOZ_ASSERT_IF(aZone
, entry
->mZone
== aZone
);
519 f(entry
->mHolder
, entry
->mTracer
, aZone
);
523 bool JSHolderMap::RemoveEntry(EntryVector
& aJSHolders
, Entry
* aEntry
) {
525 MOZ_ASSERT(!aEntry
->mHolder
);
527 // Remove all dead entries from the end of the vector.
528 while (!aJSHolders
.GetLast().mHolder
&& &aJSHolders
.GetLast() != aEntry
) {
529 aJSHolders
.PopLast();
532 // Swap the element we want to remove with the last one and update the hash
534 Entry
* lastEntry
= &aJSHolders
.GetLast();
535 if (aEntry
!= lastEntry
) {
536 MOZ_ASSERT(lastEntry
->mHolder
);
537 *aEntry
= *lastEntry
;
538 MOZ_ASSERT(mJSHolderMap
.has(aEntry
->mHolder
));
539 MOZ_ALWAYS_TRUE(mJSHolderMap
.put(aEntry
->mHolder
, aEntry
));
542 aJSHolders
.PopLast();
544 // Return whether aEntry is still in the vector.
545 return aEntry
!= lastEntry
;
548 inline bool JSHolderMap::Has(void* aHolder
) const {
549 return mJSHolderMap
.has(aHolder
);
552 inline nsScriptObjectTracer
* JSHolderMap::Get(void* aHolder
) const {
553 auto ptr
= mJSHolderMap
.lookup(aHolder
);
558 Entry
* entry
= ptr
->value();
559 MOZ_ASSERT(entry
->mHolder
== aHolder
);
560 return entry
->mTracer
;
563 inline nsScriptObjectTracer
* JSHolderMap::GetAndRemove(void* aHolder
) {
566 auto ptr
= mJSHolderMap
.lookup(aHolder
);
571 Entry
* entry
= ptr
->value();
572 MOZ_ASSERT(entry
->mHolder
== aHolder
);
573 nsScriptObjectTracer
* tracer
= entry
->mTracer
;
575 // Clear the entry's contents. It will be removed during the next iteration.
578 mJSHolderMap
.remove(ptr
);
583 inline void JSHolderMap::Put(void* aHolder
, nsScriptObjectTracer
* aTracer
,
588 // Don't associate multi-zone holders with a zone, even if one is supplied.
589 if (aTracer
->IsMultiZoneJSHolder()) {
593 auto ptr
= mJSHolderMap
.lookupForAdd(aHolder
);
595 Entry
* entry
= ptr
->value();
597 MOZ_ASSERT(entry
->mHolder
== aHolder
);
598 MOZ_ASSERT(entry
->mTracer
== aTracer
,
599 "Don't call HoldJSObjects in superclass ctors");
602 MOZ_ASSERT(entry
->mZone
== aZone
);
604 entry
->mZone
= aZone
;
608 entry
->mTracer
= aTracer
;
612 EntryVector
* vector
= &mAnyZoneJSHolders
;
614 auto ptr
= mPerZoneJSHolders
.lookupForAdd(aZone
);
617 mPerZoneJSHolders
.add(ptr
, aZone
, MakeUnique
<EntryVector
>()));
619 vector
= ptr
->value().get();
622 vector
->InfallibleAppend(Entry
{aHolder
, aTracer
, aZone
});
623 MOZ_ALWAYS_TRUE(mJSHolderMap
.add(ptr
, aHolder
, &vector
->GetLast()));
626 size_t JSHolderMap::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf
) const {
629 // We're deliberately not measuring anything hanging off the entries in
631 n
+= mJSHolderMap
.shallowSizeOfExcludingThis(aMallocSizeOf
);
632 n
+= mAnyZoneJSHolders
.SizeOfExcludingThis(aMallocSizeOf
);
633 n
+= mPerZoneJSHolders
.shallowSizeOfExcludingThis(aMallocSizeOf
);
634 for (auto i
= mPerZoneJSHolders
.iter(); !i
.done(); i
.next()) {
635 n
+= i
.get().value()->SizeOfExcludingThis(aMallocSizeOf
);
641 CycleCollectedJSRuntime::CycleCollectedJSRuntime(JSContext
* aCx
)
643 mGCThingCycleCollectorGlobal(sGCThingCycleCollectorGlobal
),
644 mJSZoneCycleCollectorGlobal(sJSZoneCycleCollectorGlobal
),
645 mJSRuntime(JS_GetRuntime(aCx
)),
646 mHasPendingIdleGCTask(false),
647 mPrevGCSliceCallback(nullptr),
648 mPrevGCNurseryCollectionCallback(nullptr),
649 mOutOfMemoryState(OOMState::OK
),
650 mLargeAllocationFailureState(OOMState::OK
)
653 mShutdownCalled(false)
656 MOZ_COUNT_CTOR(CycleCollectedJSRuntime
);
658 MOZ_ASSERT(mJSRuntime
);
660 #if defined(XP_MACOSX)
661 if (!XRE_IsParentProcess()) {
662 nsMacUtilsImpl::EnableTCSMIfAvailable();
666 if (!JS_AddExtraGCRootsTracer(aCx
, TraceBlackJS
, this)) {
667 MOZ_CRASH("JS_AddExtraGCRootsTracer failed");
669 JS_SetGrayGCRootsTracer(aCx
, TraceGrayJS
, this);
670 JS_SetGCCallback(aCx
, GCCallback
, this);
671 mPrevGCSliceCallback
= JS::SetGCSliceCallback(aCx
, GCSliceCallback
);
673 if (NS_IsMainThread()) {
674 // We would like to support all threads here, but the way timeline consumers
675 // are set up currently, you can either add a marker for one specific
676 // docshell, or for every consumer globally. We would like to add a marker
677 // for every consumer observing anything on this thread, but that is not
678 // currently possible. For now, add global markers only when we are on the
679 // main thread, since the UI for this tracing data only displays data
680 // relevant to the main-thread.
681 mPrevGCNurseryCollectionCallback
=
682 JS::SetGCNurseryCollectionCallback(aCx
, GCNurseryCollectionCallback
);
685 JS_SetObjectsTenuredCallback(aCx
, JSObjectsTenuredCb
, this);
686 JS::SetOutOfMemoryCallback(aCx
, OutOfMemoryCallback
, this);
687 JS::SetWaitCallback(mJSRuntime
, BeforeWaitCallback
, AfterWaitCallback
,
688 sizeof(dom::AutoYieldJSThreadExecution
));
689 JS::SetWarningReporter(aCx
, MozCrashWarningReporter
);
691 js::AutoEnterOOMUnsafeRegion::setAnnotateOOMAllocationSizeCallback(
692 CrashReporter::AnnotateOOMAllocationSize
);
694 static js::DOMCallbacks DOMcallbacks
= {InstanceClassHasProtoAtDepth
};
695 SetDOMCallbacks(aCx
, &DOMcallbacks
);
696 js::SetScriptEnvironmentPreparer(aCx
, &mEnvironmentPreparer
);
698 JS::dbg::SetDebuggerMallocSizeOf(aCx
, moz_malloc_size_of
);
700 #ifdef MOZ_JS_DEV_ERROR_INTERCEPTOR
701 JS_SetErrorInterceptorCallback(mJSRuntime
, &mErrorInterceptor
);
702 #endif // MOZ_JS_DEV_ERROR_INTERCEPTOR
704 JS_SetDestroyZoneCallback(aCx
, OnZoneDestroyed
);
707 #ifdef NS_BUILD_REFCNT_LOGGING
708 class JSLeakTracer
: public JS::CallbackTracer
{
710 explicit JSLeakTracer(JSRuntime
* aRuntime
)
711 : JS::CallbackTracer(aRuntime
, JS::TracerKind::Callback
,
712 JS::WeakMapTraceAction::TraceKeysAndValues
) {}
715 void onChild(const JS::GCCellPtr
& thing
) override
{
716 const char* kindName
= JS::GCTraceKindToAscii(thing
.kind());
717 size_t size
= JS::GCTraceKindSize(thing
.kind());
718 MOZ_LOG_CTOR(thing
.asCell(), kindName
, size
);
723 void CycleCollectedJSRuntime::Shutdown(JSContext
* cx
) {
724 #ifdef MOZ_JS_DEV_ERROR_INTERCEPTOR
725 mErrorInterceptor
.Shutdown(mJSRuntime
);
726 #endif // MOZ_JS_DEV_ERROR_INTERCEPTOR
728 // There should not be any roots left to trace at this point. Ensure any that
729 // remain are flagged as leaks.
730 #ifdef NS_BUILD_REFCNT_LOGGING
731 JSLeakTracer
tracer(Runtime());
732 TraceNativeBlackRoots(&tracer
);
733 TraceNativeGrayRoots(&tracer
, JSHolderMap::AllHolders
);
737 mShutdownCalled
= true;
740 JS_SetDestroyZoneCallback(cx
, nullptr);
743 CycleCollectedJSRuntime::~CycleCollectedJSRuntime() {
744 MOZ_COUNT_DTOR(CycleCollectedJSRuntime
);
745 MOZ_ASSERT(!mDeferredFinalizerTable
.Count());
746 MOZ_ASSERT(!mFinalizeRunnable
);
747 MOZ_ASSERT(mShutdownCalled
);
750 void CycleCollectedJSRuntime::SetContext(CycleCollectedJSContext
* aContext
) {
751 MOZ_ASSERT(!mContext
|| !aContext
, "Don't replace the context!");
755 size_t CycleCollectedJSRuntime::SizeOfExcludingThis(
756 MallocSizeOf aMallocSizeOf
) const {
757 return mJSHolders
.SizeOfExcludingThis(aMallocSizeOf
);
760 void CycleCollectedJSRuntime::UnmarkSkippableJSHolders() {
761 mJSHolders
.ForEach([](void* holder
, nsScriptObjectTracer
* tracer
,
762 JS::Zone
* zone
) { tracer
->CanSkip(holder
, true); });
765 void CycleCollectedJSRuntime::DescribeGCThing(
766 bool aIsMarked
, JS::GCCellPtr aThing
,
767 nsCycleCollectionTraversalCallback
& aCb
) const {
768 if (!aCb
.WantDebugInfo()) {
769 aCb
.DescribeGCedNode(aIsMarked
, "JS Object");
774 uint64_t compartmentAddress
= 0;
775 if (aThing
.is
<JSObject
>()) {
776 JSObject
* obj
= &aThing
.as
<JSObject
>();
777 compartmentAddress
= (uint64_t)JS::GetCompartment(obj
);
778 const JSClass
* clasp
= JS::GetClass(obj
);
780 // Give the subclass a chance to do something
781 if (DescribeCustomObjects(obj
, clasp
, name
)) {
782 // Nothing else to do!
783 } else if (js::IsFunctionObject(obj
)) {
784 JSFunction
* fun
= JS_GetObjectFunction(obj
);
785 JSString
* str
= JS_GetFunctionDisplayId(fun
);
787 JSLinearString
* linear
= JS_ASSERT_STRING_IS_LINEAR(str
);
789 AssignJSLinearString(chars
, linear
);
790 NS_ConvertUTF16toUTF8
fname(chars
);
791 SprintfLiteral(name
, "JS Object (Function - %s)", fname
.get());
793 SprintfLiteral(name
, "JS Object (Function)");
796 SprintfLiteral(name
, "JS Object (%s)", clasp
->name
);
799 SprintfLiteral(name
, "%s", JS::GCTraceKindToAscii(aThing
.kind()));
802 // Disable printing global for objects while we figure out ObjShrink fallout.
803 aCb
.DescribeGCedNode(aIsMarked
, name
, compartmentAddress
);
806 void CycleCollectedJSRuntime::NoteGCThingJSChildren(
807 JS::GCCellPtr aThing
, nsCycleCollectionTraversalCallback
& aCb
) const {
808 TraversalTracer
trc(mJSRuntime
, aCb
);
809 JS::TraceChildren(&trc
, aThing
);
812 void CycleCollectedJSRuntime::NoteGCThingXPCOMChildren(
813 const JSClass
* aClasp
, JSObject
* aObj
,
814 nsCycleCollectionTraversalCallback
& aCb
) const {
816 MOZ_ASSERT(aClasp
== JS::GetClass(aObj
));
818 JS::Rooted
<JSObject
*> obj(RootingCx(), aObj
);
820 if (NoteCustomGCThingXPCOMChildren(aClasp
, obj
, aCb
)) {
821 // Nothing else to do!
825 // XXX This test does seem fragile, we should probably whitelist classes
826 // that do hold a strong reference, but that might not be possible.
827 if (aClasp
->flags
& JSCLASS_HAS_PRIVATE
&&
828 aClasp
->flags
& JSCLASS_PRIVATE_IS_NSISUPPORTS
) {
829 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb
, "JS::GetPrivate(obj)");
830 aCb
.NoteXPCOMChild(static_cast<nsISupports
*>(JS::GetPrivate(obj
)));
834 const DOMJSClass
* domClass
= GetDOMClass(aClasp
);
836 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb
, "UnwrapDOMObject(obj)");
837 // It's possible that our object is an unforgeable holder object, in
838 // which case it doesn't actually have a C++ DOM object associated with
839 // it. Use UnwrapPossiblyNotInitializedDOMObject, which produces null in
840 // that case, since NoteXPCOMChild/NoteNativeChild are null-safe.
841 if (domClass
->mDOMObjectIsISupports
) {
843 UnwrapPossiblyNotInitializedDOMObject
<nsISupports
>(obj
));
844 } else if (domClass
->mParticipant
) {
845 aCb
.NoteNativeChild(UnwrapPossiblyNotInitializedDOMObject
<void>(obj
),
846 domClass
->mParticipant
);
851 if (IsRemoteObjectProxy(obj
)) {
853 static_cast<const RemoteObjectProxyBase
*>(js::GetProxyHandler(obj
));
854 return handler
->NoteChildren(obj
, aCb
);
857 JS::Value value
= js::MaybeGetScriptPrivate(obj
);
858 if (!value
.isUndefined()) {
859 aCb
.NoteXPCOMChild(static_cast<nsISupports
*>(value
.toPrivate()));
863 void CycleCollectedJSRuntime::TraverseGCThing(
864 TraverseSelect aTs
, JS::GCCellPtr aThing
,
865 nsCycleCollectionTraversalCallback
& aCb
) {
866 bool isMarkedGray
= JS::GCThingIsMarkedGray(aThing
);
868 if (aTs
== TRAVERSE_FULL
) {
869 DescribeGCThing(!isMarkedGray
, aThing
, aCb
);
872 // If this object is alive, then all of its children are alive. For JS
873 // objects, the black-gray invariant ensures the children are also marked
874 // black. For C++ objects, the ref count from this object will keep them
875 // alive. Thus we don't need to trace our children, unless we are debugging
876 // using WantAllTraces.
877 if (!isMarkedGray
&& !aCb
.WantAllTraces()) {
881 if (aTs
== TRAVERSE_FULL
) {
882 NoteGCThingJSChildren(aThing
, aCb
);
885 if (aThing
.is
<JSObject
>()) {
886 JSObject
* obj
= &aThing
.as
<JSObject
>();
887 NoteGCThingXPCOMChildren(JS::GetClass(obj
), obj
, aCb
);
891 struct TraverseObjectShimClosure
{
892 nsCycleCollectionTraversalCallback
& cb
;
893 CycleCollectedJSRuntime
* self
;
896 void CycleCollectedJSRuntime::TraverseZone(
897 JS::Zone
* aZone
, nsCycleCollectionTraversalCallback
& aCb
) {
899 * We treat the zone as being gray. We handle non-gray GCthings in the
900 * zone by not reporting their children to the CC. The black-gray invariant
901 * ensures that any JS children will also be non-gray, and thus don't need to
902 * be added to the graph. For C++ children, not representing the edge from the
903 * non-gray JS GCthings to the C++ object will keep the child alive.
905 * We don't allow zone merging in a WantAllTraces CC, because then these
906 * assumptions don't hold.
908 aCb
.DescribeGCedNode(false, "JS Zone");
911 * Every JS child of everything in the zone is either in the zone
912 * or is a cross-compartment wrapper. In the former case, we don't need to
913 * represent these edges in the CC graph because JS objects are not ref
914 * counted. In the latter case, the JS engine keeps a map of these wrappers,
915 * which we iterate over. Edges between compartments in the same zone will add
916 * unnecessary loop edges to the graph (bug 842137).
918 TraversalTracer
trc(mJSRuntime
, aCb
);
919 js::TraceGrayWrapperTargets(&trc
, aZone
);
922 * To find C++ children of things in the zone, we scan every JS Object in
923 * the zone. Only JS Objects can have C++ children.
925 TraverseObjectShimClosure closure
= {aCb
, this};
926 js::IterateGrayObjects(aZone
, TraverseObjectShim
, &closure
);
930 void CycleCollectedJSRuntime::TraverseObjectShim(
931 void* aData
, JS::GCCellPtr aThing
, const JS::AutoRequireNoGC
& nogc
) {
932 TraverseObjectShimClosure
* closure
=
933 static_cast<TraverseObjectShimClosure
*>(aData
);
935 MOZ_ASSERT(aThing
.is
<JSObject
>());
936 closure
->self
->TraverseGCThing(CycleCollectedJSRuntime::TRAVERSE_CPP
, aThing
,
940 void CycleCollectedJSRuntime::TraverseNativeRoots(
941 nsCycleCollectionNoteRootCallback
& aCb
) {
942 // NB: This is here just to preserve the existing XPConnect order. I doubt it
943 // would hurt to do this after the JS holders.
944 TraverseAdditionalNativeRoots(aCb
);
947 [&aCb
](void* holder
, nsScriptObjectTracer
* tracer
, JS::Zone
* zone
) {
948 bool noteRoot
= false;
949 if (MOZ_UNLIKELY(aCb
.WantAllTraces())) {
952 tracer
->Trace(holder
,
953 TraceCallbackFunc(CheckParticipatesInCycleCollection
),
958 aCb
.NoteNativeRoot(holder
, tracer
);
964 void CycleCollectedJSRuntime::TraceBlackJS(JSTracer
* aTracer
, void* aData
) {
965 CycleCollectedJSRuntime
* self
= static_cast<CycleCollectedJSRuntime
*>(aData
);
967 self
->TraceNativeBlackRoots(aTracer
);
971 void CycleCollectedJSRuntime::TraceGrayJS(JSTracer
* aTracer
, void* aData
) {
972 CycleCollectedJSRuntime
* self
= static_cast<CycleCollectedJSRuntime
*>(aData
);
974 // Mark these roots as gray so the CC can walk them later.
975 self
->TraceNativeGrayRoots(aTracer
, JSHolderMap::HoldersInCollectingZones
);
979 void CycleCollectedJSRuntime::GCCallback(JSContext
* aContext
,
981 JS::GCReason aReason
, void* aData
) {
982 CycleCollectedJSRuntime
* self
= static_cast<CycleCollectedJSRuntime
*>(aData
);
984 MOZ_ASSERT(CycleCollectedJSContext::Get()->Context() == aContext
);
985 MOZ_ASSERT(CycleCollectedJSContext::Get()->Runtime() == self
);
987 self
->OnGC(aContext
, aStatus
, aReason
);
991 void CycleCollectedJSRuntime::GCSliceCallback(JSContext
* aContext
,
992 JS::GCProgress aProgress
,
993 const JS::GCDescription
& aDesc
) {
994 CycleCollectedJSRuntime
* self
= CycleCollectedJSRuntime::Get();
995 MOZ_ASSERT(CycleCollectedJSContext::Get()->Context() == aContext
);
997 #ifdef MOZ_GECKO_PROFILER
998 if (profiler_thread_is_being_profiled()) {
999 if (aProgress
== JS::GC_CYCLE_END
) {
1000 struct GCMajorMarker
{
1001 static constexpr mozilla::Span
<const char> MarkerTypeName() {
1002 return mozilla::MakeStringSpan("GCMajor");
1004 static void StreamJSONMarkerData(
1005 mozilla::baseprofiler::SpliceableJSONWriter
& aWriter
,
1006 const mozilla::ProfilerString8View
& aTimingJSON
) {
1007 if (aTimingJSON
.Length() != 0) {
1008 aWriter
.SplicedJSONProperty("timings", aTimingJSON
);
1010 aWriter
.NullProperty("timings");
1013 static mozilla::MarkerSchema
MarkerTypeDisplay() {
1014 using MS
= mozilla::MarkerSchema
;
1015 MS schema
{MS::Location::markerChart
, MS::Location::markerTable
,
1016 MS::Location::timelineMemory
};
1017 // No display instructions here, there is special handling in the
1023 profiler_add_marker("GCMajor", baseprofiler::category::GCCC
,
1024 MarkerTiming::Interval(aDesc
.startTime(aContext
),
1025 aDesc
.endTime(aContext
)),
1027 ProfilerString8View::WrapNullTerminatedString(
1028 aDesc
.formatJSONProfiler(aContext
).get()));
1029 } else if (aProgress
== JS::GC_SLICE_END
) {
1030 struct GCSliceMarker
{
1031 static constexpr mozilla::Span
<const char> MarkerTypeName() {
1032 return mozilla::MakeStringSpan("GCSlice");
1034 static void StreamJSONMarkerData(
1035 mozilla::baseprofiler::SpliceableJSONWriter
& aWriter
,
1036 const mozilla::ProfilerString8View
& aTimingJSON
) {
1037 if (aTimingJSON
.Length() != 0) {
1038 aWriter
.SplicedJSONProperty("timings", aTimingJSON
);
1040 aWriter
.NullProperty("timings");
1043 static mozilla::MarkerSchema
MarkerTypeDisplay() {
1044 using MS
= mozilla::MarkerSchema
;
1045 MS schema
{MS::Location::markerChart
, MS::Location::markerTable
,
1046 MS::Location::timelineMemory
};
1047 // No display instructions here, there is special handling in the
1053 profiler_add_marker("GCSlice", baseprofiler::category::GCCC
,
1054 MarkerTiming::Interval(aDesc
.lastSliceStart(aContext
),
1055 aDesc
.lastSliceEnd(aContext
)),
1057 ProfilerString8View::WrapNullTerminatedString(
1058 aDesc
.sliceToJSONProfiler(aContext
).get()));
1063 if (aProgress
== JS::GC_CYCLE_END
&&
1064 JS::dbg::FireOnGarbageCollectionHookRequired(aContext
)) {
1065 JS::GCReason reason
= aDesc
.reason_
;
1066 Unused
<< NS_WARN_IF(
1067 NS_FAILED(DebuggerOnGCRunnable::Enqueue(aContext
, aDesc
)) &&
1068 reason
!= JS::GCReason::SHUTDOWN_CC
&&
1069 reason
!= JS::GCReason::DESTROY_RUNTIME
&&
1070 reason
!= JS::GCReason::XPCONNECT_SHUTDOWN
);
1073 if (self
->mPrevGCSliceCallback
) {
1074 self
->mPrevGCSliceCallback(aContext
, aProgress
, aDesc
);
1078 class MinorGCMarker
: public TimelineMarker
{
1080 JS::GCReason mReason
;
1083 MinorGCMarker(MarkerTracingType aTracingType
, JS::GCReason aReason
)
1084 : TimelineMarker("MinorGC", aTracingType
, MarkerStackRequest::NO_STACK
),
1086 MOZ_ASSERT(aTracingType
== MarkerTracingType::START
||
1087 aTracingType
== MarkerTracingType::END
);
1090 MinorGCMarker(JS::GCNurseryProgress aProgress
, JS::GCReason aReason
)
1093 aProgress
== JS::GCNurseryProgress::GC_NURSERY_COLLECTION_START
1094 ? MarkerTracingType::START
1095 : MarkerTracingType::END
,
1096 MarkerStackRequest::NO_STACK
),
1099 virtual void AddDetails(JSContext
* aCx
,
1100 dom::ProfileTimelineMarker
& aMarker
) override
{
1101 TimelineMarker::AddDetails(aCx
, aMarker
);
1103 if (GetTracingType() == MarkerTracingType::START
) {
1104 auto reason
= JS::ExplainGCReason(mReason
);
1105 aMarker
.mCauseName
.Construct(NS_ConvertUTF8toUTF16(reason
));
1109 virtual UniquePtr
<AbstractTimelineMarker
> Clone() override
{
1110 auto clone
= MakeUnique
<MinorGCMarker
>(GetTracingType(), mReason
);
1111 clone
->SetCustomTime(GetTime());
1112 return UniquePtr
<AbstractTimelineMarker
>(std::move(clone
));
1117 void CycleCollectedJSRuntime::GCNurseryCollectionCallback(
1118 JSContext
* aContext
, JS::GCNurseryProgress aProgress
,
1119 JS::GCReason aReason
) {
1120 CycleCollectedJSRuntime
* self
= CycleCollectedJSRuntime::Get();
1121 MOZ_ASSERT(CycleCollectedJSContext::Get()->Context() == aContext
);
1122 MOZ_ASSERT(NS_IsMainThread());
1124 RefPtr
<TimelineConsumers
> timelines
= TimelineConsumers::Get();
1125 if (timelines
&& !timelines
->IsEmpty()) {
1126 UniquePtr
<AbstractTimelineMarker
> abstractMarker(
1127 MakeUnique
<MinorGCMarker
>(aProgress
, aReason
));
1128 timelines
->AddMarkerForAllObservedDocShells(abstractMarker
);
1131 if (aProgress
== JS::GCNurseryProgress::GC_NURSERY_COLLECTION_START
) {
1132 self
->mLatestNurseryCollectionStart
= TimeStamp::Now();
1134 #ifdef MOZ_GECKO_PROFILER
1135 else if (aProgress
== JS::GCNurseryProgress::GC_NURSERY_COLLECTION_END
&&
1136 profiler_thread_is_being_profiled()) {
1137 struct GCMinorMarker
{
1138 static constexpr mozilla::Span
<const char> MarkerTypeName() {
1139 return mozilla::MakeStringSpan("GCMinor");
1141 static void StreamJSONMarkerData(
1142 mozilla::baseprofiler::SpliceableJSONWriter
& aWriter
,
1143 const mozilla::ProfilerString8View
& aTimingJSON
) {
1144 if (aTimingJSON
.Length() != 0) {
1145 aWriter
.SplicedJSONProperty("nursery", aTimingJSON
);
1147 aWriter
.NullProperty("nursery");
1150 static mozilla::MarkerSchema
MarkerTypeDisplay() {
1151 using MS
= mozilla::MarkerSchema
;
1152 MS schema
{MS::Location::markerChart
, MS::Location::markerTable
,
1153 MS::Location::timelineMemory
};
1154 // No display instructions here, there is special handling in the
1160 profiler_add_marker(
1161 "GCMinor", baseprofiler::category::GCCC
,
1162 MarkerTiming::IntervalUntilNowFrom(self
->mLatestNurseryCollectionStart
),
1164 ProfilerString8View::WrapNullTerminatedString(
1165 JS::MinorGcToJSON(aContext
).get()));
1169 if (self
->mPrevGCNurseryCollectionCallback
) {
1170 self
->mPrevGCNurseryCollectionCallback(aContext
, aProgress
, aReason
);
1175 void CycleCollectedJSRuntime::OutOfMemoryCallback(JSContext
* aContext
,
1177 CycleCollectedJSRuntime
* self
= static_cast<CycleCollectedJSRuntime
*>(aData
);
1179 MOZ_ASSERT(CycleCollectedJSContext::Get()->Context() == aContext
);
1180 MOZ_ASSERT(CycleCollectedJSContext::Get()->Runtime() == self
);
1182 self
->OnOutOfMemory();
1186 void* CycleCollectedJSRuntime::BeforeWaitCallback(uint8_t* aMemory
) {
1187 MOZ_ASSERT(aMemory
);
1189 // aMemory is stack allocated memory to contain our RAII object. This allows
1190 // for us to avoid allocations on the heap during this callback.
1191 return new (aMemory
) dom::AutoYieldJSThreadExecution
;
1195 void CycleCollectedJSRuntime::AfterWaitCallback(void* aCookie
) {
1196 MOZ_ASSERT(aCookie
);
1197 static_cast<dom::AutoYieldJSThreadExecution
*>(aCookie
)
1198 ->~AutoYieldJSThreadExecution();
1201 struct JsGcTracer
: public TraceCallbacks
{
1202 virtual void Trace(JS::Heap
<JS::Value
>* aPtr
, const char* aName
,
1203 void* aClosure
) const override
{
1204 JS::TraceEdge(static_cast<JSTracer
*>(aClosure
), aPtr
, aName
);
1206 virtual void Trace(JS::Heap
<jsid
>* aPtr
, const char* aName
,
1207 void* aClosure
) const override
{
1208 JS::TraceEdge(static_cast<JSTracer
*>(aClosure
), aPtr
, aName
);
1210 virtual void Trace(JS::Heap
<JSObject
*>* aPtr
, const char* aName
,
1211 void* aClosure
) const override
{
1212 JS::TraceEdge(static_cast<JSTracer
*>(aClosure
), aPtr
, aName
);
1214 virtual void Trace(nsWrapperCache
* aPtr
, const char* aName
,
1215 void* aClosure
) const override
{
1216 aPtr
->TraceWrapper(static_cast<JSTracer
*>(aClosure
), aName
);
1218 virtual void Trace(JS::TenuredHeap
<JSObject
*>* aPtr
, const char* aName
,
1219 void* aClosure
) const override
{
1220 JS::TraceEdge(static_cast<JSTracer
*>(aClosure
), aPtr
, aName
);
1222 virtual void Trace(JS::Heap
<JSString
*>* aPtr
, const char* aName
,
1223 void* aClosure
) const override
{
1224 JS::TraceEdge(static_cast<JSTracer
*>(aClosure
), aPtr
, aName
);
1226 virtual void Trace(JS::Heap
<JSScript
*>* aPtr
, const char* aName
,
1227 void* aClosure
) const override
{
1228 JS::TraceEdge(static_cast<JSTracer
*>(aClosure
), aPtr
, aName
);
1230 virtual void Trace(JS::Heap
<JSFunction
*>* aPtr
, const char* aName
,
1231 void* aClosure
) const override
{
1232 JS::TraceEdge(static_cast<JSTracer
*>(aClosure
), aPtr
, aName
);
1236 void mozilla::TraceScriptHolder(nsISupports
* aHolder
, JSTracer
* aTracer
) {
1237 nsXPCOMCycleCollectionParticipant
* participant
= nullptr;
1238 CallQueryInterface(aHolder
, &participant
);
1239 participant
->Trace(aHolder
, JsGcTracer(), aTracer
);
1242 #if defined(NIGHTLY_BUILD) || defined(MOZ_DEV_EDITION) || defined(DEBUG)
1243 # define CHECK_SINGLE_ZONE_JS_HOLDERS
1246 #ifdef CHECK_SINGLE_ZONE_JS_HOLDERS
1248 // A tracer that checks that a JS holder only holds JS GC things in a single
1250 struct CheckZoneTracer
: public TraceCallbacks
{
1251 const char* mClassName
;
1252 mutable JS::Zone
* mZone
;
1254 explicit CheckZoneTracer(const char* aClassName
, JS::Zone
* aZone
= nullptr)
1255 : mClassName(aClassName
), mZone(aZone
) {}
1257 void checkZone(JS::Zone
* aZone
, const char* aName
) const {
1263 if (aZone
== mZone
) {
1267 // Most JS holders only contain pointers to GC things in a single zone. In
1268 // the future this will allow us to improve GC performance by only tracing
1269 // holders in zones that are being collected.
1271 // If you added a holder that has pointers into multiple zones please try to
1272 // remedy this. Some options are:
1274 // - wrap all JS GC things into the same compartment
1275 // - split GC thing pointers between separate cycle collected objects
1277 // If all else fails, flag the class as containing pointers into multiple
1278 // zones by using NS_IMPL_CYCLE_COLLECTION_MULTI_ZONE_JSHOLDER_CLASS.
1279 MOZ_CRASH_UNSAFE_PRINTF(
1280 "JS holder %s contains pointers to GC things in more than one zone ("
1285 virtual void Trace(JS::Heap
<JS::Value
>* aPtr
, const char* aName
,
1286 void* aClosure
) const override
{
1287 JS::Value value
= aPtr
->unbarrieredGet();
1288 if (value
.isGCThing()) {
1289 checkZone(JS::GetGCThingZone(value
.toGCCellPtr()), aName
);
1292 virtual void Trace(JS::Heap
<jsid
>* aPtr
, const char* aName
,
1293 void* aClosure
) const override
{
1294 jsid id
= aPtr
->unbarrieredGet();
1295 if (id
.isGCThing()) {
1296 checkZone(JS::GetTenuredGCThingZone(id
.toGCCellPtr()), aName
);
1299 virtual void Trace(JS::Heap
<JSObject
*>* aPtr
, const char* aName
,
1300 void* aClosure
) const override
{
1301 JSObject
* obj
= aPtr
->unbarrieredGet();
1303 checkZone(js::GetObjectZoneFromAnyThread(obj
), aName
);
1306 virtual void Trace(nsWrapperCache
* aPtr
, const char* aName
,
1307 void* aClosure
) const override
{
1308 JSObject
* obj
= aPtr
->GetWrapperPreserveColor();
1310 checkZone(js::GetObjectZoneFromAnyThread(obj
), aName
);
1313 virtual void Trace(JS::TenuredHeap
<JSObject
*>* aPtr
, const char* aName
,
1314 void* aClosure
) const override
{
1315 JSObject
* obj
= aPtr
->unbarrieredGetPtr();
1317 checkZone(js::GetObjectZoneFromAnyThread(obj
), aName
);
1320 virtual void Trace(JS::Heap
<JSString
*>* aPtr
, const char* aName
,
1321 void* aClosure
) const override
{
1322 JSString
* str
= aPtr
->unbarrieredGet();
1324 checkZone(JS::GetStringZone(str
), aName
);
1327 virtual void Trace(JS::Heap
<JSScript
*>* aPtr
, const char* aName
,
1328 void* aClosure
) const override
{
1329 JSScript
* script
= aPtr
->unbarrieredGet();
1331 checkZone(JS::GetTenuredGCThingZone(JS::GCCellPtr(script
)), aName
);
1334 virtual void Trace(JS::Heap
<JSFunction
*>* aPtr
, const char* aName
,
1335 void* aClosure
) const override
{
1336 JSFunction
* fun
= aPtr
->unbarrieredGet();
1338 checkZone(js::GetObjectZoneFromAnyThread(JS_GetFunctionObject(fun
)),
1344 static inline void CheckHolderIsSingleZone(
1345 void* aHolder
, nsCycleCollectionParticipant
* aParticipant
,
1347 CheckZoneTracer
tracer(aParticipant
->ClassName(), aZone
);
1348 aParticipant
->Trace(aHolder
, tracer
, nullptr);
1353 static inline bool ShouldCheckSingleZoneHolders() {
1356 #elif defined(NIGHTLY_BUILD) || defined(MOZ_DEV_EDITION)
1357 // Don't check every time to avoid performance impact.
1358 return rand() % 256 == 0;
1364 void CycleCollectedJSRuntime::TraceNativeGrayRoots(
1365 JSTracer
* aTracer
, JSHolderMap::WhichHolders aWhich
) {
1366 // NB: This is here just to preserve the existing XPConnect order. I doubt it
1367 // would hurt to do this after the JS holders.
1368 TraceAdditionalNativeGrayRoots(aTracer
);
1370 bool checkSingleZoneHolders
= ShouldCheckSingleZoneHolders();
1372 [aTracer
, checkSingleZoneHolders
](
1373 void* holder
, nsScriptObjectTracer
* tracer
, JS::Zone
* zone
) {
1374 #ifdef CHECK_SINGLE_ZONE_JS_HOLDERS
1375 if (checkSingleZoneHolders
&& !tracer
->IsMultiZoneJSHolder()) {
1376 CheckHolderIsSingleZone(holder
, tracer
, zone
);
1379 Unused
<< checkSingleZoneHolders
;
1381 tracer
->Trace(holder
, JsGcTracer(), aTracer
);
1386 void CycleCollectedJSRuntime::AddJSHolder(void* aHolder
,
1387 nsScriptObjectTracer
* aTracer
,
1389 mJSHolders
.Put(aHolder
, aTracer
, aZone
);
1392 struct ClearJSHolder
: public TraceCallbacks
{
1393 virtual void Trace(JS::Heap
<JS::Value
>* aPtr
, const char*,
1394 void*) const override
{
1395 aPtr
->setUndefined();
1398 virtual void Trace(JS::Heap
<jsid
>* aPtr
, const char*, void*) const override
{
1402 virtual void Trace(JS::Heap
<JSObject
*>* aPtr
, const char*,
1403 void*) const override
{
1407 virtual void Trace(nsWrapperCache
* aPtr
, const char* aName
,
1408 void* aClosure
) const override
{
1409 aPtr
->ClearWrapper();
1412 virtual void Trace(JS::TenuredHeap
<JSObject
*>* aPtr
, const char*,
1413 void*) const override
{
1417 virtual void Trace(JS::Heap
<JSString
*>* aPtr
, const char*,
1418 void*) const override
{
1422 virtual void Trace(JS::Heap
<JSScript
*>* aPtr
, const char*,
1423 void*) const override
{
1427 virtual void Trace(JS::Heap
<JSFunction
*>* aPtr
, const char*,
1428 void*) const override
{
1433 void CycleCollectedJSRuntime::RemoveJSHolder(void* aHolder
) {
1434 nsScriptObjectTracer
* tracer
= mJSHolders
.GetAndRemove(aHolder
);
1436 // Bug 1531951: The analysis can't see through the virtual call but we know
1437 // that the ClearJSHolder tracer will never GC.
1438 JS::AutoSuppressGCAnalysis nogc
;
1439 tracer
->Trace(aHolder
, ClearJSHolder(), nullptr);
1444 static void AssertNoGcThing(JS::GCCellPtr aGCThing
, const char* aName
,
1446 MOZ_ASSERT(!aGCThing
);
1449 void CycleCollectedJSRuntime::AssertNoObjectsToTrace(void* aPossibleJSHolder
) {
1450 nsScriptObjectTracer
* tracer
= mJSHolders
.Get(aPossibleJSHolder
);
1452 tracer
->Trace(aPossibleJSHolder
, TraceCallbackFunc(AssertNoGcThing
),
1458 nsCycleCollectionParticipant
* CycleCollectedJSRuntime::GCThingParticipant() {
1459 return &mGCThingCycleCollectorGlobal
;
1462 nsCycleCollectionParticipant
* CycleCollectedJSRuntime::ZoneParticipant() {
1463 return &mJSZoneCycleCollectorGlobal
;
1466 nsresult
CycleCollectedJSRuntime::TraverseRoots(
1467 nsCycleCollectionNoteRootCallback
& aCb
) {
1468 TraverseNativeRoots(aCb
);
1470 NoteWeakMapsTracer
trc(mJSRuntime
, aCb
);
1471 js::TraceWeakMaps(&trc
);
1476 bool CycleCollectedJSRuntime::UsefulToMergeZones() const { return false; }
1478 void CycleCollectedJSRuntime::FixWeakMappingGrayBits() const {
1479 MOZ_ASSERT(!JS::IsIncrementalGCInProgress(mJSRuntime
),
1480 "Don't call FixWeakMappingGrayBits during a GC.");
1481 FixWeakMappingGrayBitsTracer
fixer(mJSRuntime
);
1485 void CycleCollectedJSRuntime::CheckGrayBits() const {
1486 MOZ_ASSERT(!JS::IsIncrementalGCInProgress(mJSRuntime
),
1487 "Don't call CheckGrayBits during a GC.");
1490 // Bug 1346874 - The gray state check is expensive. Android tests are already
1491 // slow enough that this check can easily push them over the threshold to a
1494 MOZ_ASSERT(js::CheckGrayMarkingState(mJSRuntime
));
1495 MOZ_ASSERT(CheckWeakMappingGrayBitsTracer::Check(mJSRuntime
));
1499 bool CycleCollectedJSRuntime::AreGCGrayBitsValid() const {
1500 return js::AreGCGrayBitsValid(mJSRuntime
);
1503 void CycleCollectedJSRuntime::GarbageCollect(JS::GCReason aReason
) const {
1504 JSContext
* cx
= CycleCollectedJSContext::Get()->Context();
1505 JS::PrepareForFullGC(cx
);
1506 JS::NonIncrementalGC(cx
, GC_NORMAL
, aReason
);
1509 void CycleCollectedJSRuntime::JSObjectsTenured() {
1510 JSContext
* cx
= CycleCollectedJSContext::Get()->Context();
1511 for (auto iter
= mNurseryObjects
.Iter(); !iter
.Done(); iter
.Next()) {
1512 nsWrapperCache
* cache
= iter
.Get();
1513 JSObject
* wrapper
= cache
->GetWrapperMaybeDead();
1514 MOZ_DIAGNOSTIC_ASSERT(wrapper
);
1515 if (!JS::ObjectIsTenured(wrapper
)) {
1516 MOZ_ASSERT(!cache
->PreservingWrapper());
1517 js::gc::FinalizeDeadNurseryObject(cx
, wrapper
);
1522 for (auto iter
= mPreservedNurseryObjects
.Iter(); !iter
.Done(); iter
.Next()) {
1523 MOZ_ASSERT(JS::ObjectIsTenured(iter
.Get().get()));
1527 mNurseryObjects
.Clear();
1528 mPreservedNurseryObjects
.Clear();
1531 void CycleCollectedJSRuntime::NurseryWrapperAdded(nsWrapperCache
* aCache
) {
1533 MOZ_ASSERT(aCache
->GetWrapperMaybeDead());
1534 MOZ_ASSERT(!JS::ObjectIsTenured(aCache
->GetWrapperMaybeDead()));
1535 mNurseryObjects
.InfallibleAppend(aCache
);
1538 void CycleCollectedJSRuntime::NurseryWrapperPreserved(JSObject
* aWrapper
) {
1539 mPreservedNurseryObjects
.InfallibleAppend(
1540 JS::PersistentRooted
<JSObject
*>(mJSRuntime
, aWrapper
));
1543 void CycleCollectedJSRuntime::DeferredFinalize(
1544 DeferredFinalizeAppendFunction aAppendFunc
, DeferredFinalizeFunction aFunc
,
1546 // Tell the analysis that the function pointers will not GC.
1547 JS::AutoSuppressGCAnalysis suppress
;
1548 mDeferredFinalizerTable
.WithEntryHandle(aFunc
, [&](auto&& entry
) {
1550 aAppendFunc(entry
.Data(), aThing
);
1552 entry
.Insert(aAppendFunc(nullptr, aThing
));
1557 void CycleCollectedJSRuntime::DeferredFinalize(nsISupports
* aSupports
) {
1558 typedef DeferredFinalizerImpl
<nsISupports
> Impl
;
1559 DeferredFinalize(Impl::AppendDeferredFinalizePointer
, Impl::DeferredFinalize
,
1563 void CycleCollectedJSRuntime::DumpJSHeap(FILE* aFile
) {
1564 JSContext
* cx
= CycleCollectedJSContext::Get()->Context();
1566 mozilla::MallocSizeOf mallocSizeOf
=
1567 PR_GetEnv("MOZ_GC_LOG_SIZE") ? moz_malloc_size_of
: nullptr;
1568 js::DumpHeap(cx
, aFile
, js::CollectNurseryBeforeDump
, mallocSizeOf
);
1571 IncrementalFinalizeRunnable::IncrementalFinalizeRunnable(
1572 CycleCollectedJSRuntime
* aRt
, DeferredFinalizerTable
& aFinalizers
)
1573 : DiscardableRunnable("IncrementalFinalizeRunnable"),
1575 mFinalizeFunctionToRun(0),
1577 for (auto iter
= aFinalizers
.Iter(); !iter
.Done(); iter
.Next()) {
1578 DeferredFinalizeFunction
& function
= iter
.Key();
1579 void*& data
= iter
.Data();
1581 DeferredFinalizeFunctionHolder
* holder
=
1582 mDeferredFinalizeFunctions
.AppendElement();
1583 holder
->run
= function
;
1584 holder
->data
= data
;
1588 MOZ_ASSERT(mDeferredFinalizeFunctions
.Length());
1591 IncrementalFinalizeRunnable::~IncrementalFinalizeRunnable() {
1592 MOZ_ASSERT(!mDeferredFinalizeFunctions
.Length());
1593 MOZ_ASSERT(!mRuntime
);
1596 void IncrementalFinalizeRunnable::ReleaseNow(bool aLimited
) {
1598 NS_WARNING("Re-entering ReleaseNow");
1602 AUTO_PROFILER_LABEL("IncrementalFinalizeRunnable::ReleaseNow",
1605 mozilla::AutoRestore
<bool> ar(mReleasing
);
1607 MOZ_ASSERT(mDeferredFinalizeFunctions
.Length() != 0,
1608 "We should have at least ReleaseSliceNow to run");
1609 MOZ_ASSERT(mFinalizeFunctionToRun
< mDeferredFinalizeFunctions
.Length(),
1610 "No more finalizers to run?");
1612 TimeDuration sliceTime
= TimeDuration::FromMilliseconds(SliceMillis
);
1613 TimeStamp started
= aLimited
? TimeStamp::Now() : TimeStamp();
1614 bool timeout
= false;
1616 const DeferredFinalizeFunctionHolder
& function
=
1617 mDeferredFinalizeFunctions
[mFinalizeFunctionToRun
];
1620 while (!timeout
&& !done
) {
1622 * We don't want to read the clock too often, so we try to
1623 * release slices of 100 items.
1625 done
= function
.run(100, function
.data
);
1626 timeout
= TimeStamp::Now() - started
>= sliceTime
;
1629 ++mFinalizeFunctionToRun
;
1635 while (!function
.run(UINT32_MAX
, function
.data
))
1637 ++mFinalizeFunctionToRun
;
1639 } while (mFinalizeFunctionToRun
< mDeferredFinalizeFunctions
.Length());
1642 if (mFinalizeFunctionToRun
== mDeferredFinalizeFunctions
.Length()) {
1643 MOZ_ASSERT(mRuntime
->mFinalizeRunnable
== this);
1644 mDeferredFinalizeFunctions
.Clear();
1645 CycleCollectedJSRuntime
* runtime
= mRuntime
;
1647 // NB: This may delete this!
1648 runtime
->mFinalizeRunnable
= nullptr;
1653 IncrementalFinalizeRunnable::Run() {
1654 if (!mDeferredFinalizeFunctions
.Length()) {
1655 /* These items were already processed synchronously in JSGC_END. */
1656 MOZ_ASSERT(!mRuntime
);
1660 MOZ_ASSERT(mRuntime
->mFinalizeRunnable
== this);
1661 TimeStamp start
= TimeStamp::Now();
1664 if (mDeferredFinalizeFunctions
.Length()) {
1665 nsresult rv
= NS_DispatchToCurrentThread(this);
1666 if (NS_FAILED(rv
)) {
1670 MOZ_ASSERT(!mRuntime
);
1673 uint32_t duration
= (uint32_t)((TimeStamp::Now() - start
).ToMilliseconds());
1674 Telemetry::Accumulate(Telemetry::DEFERRED_FINALIZE_ASYNC
, duration
);
1679 void CycleCollectedJSRuntime::FinalizeDeferredThings(
1680 CycleCollectedJSContext::DeferredFinalizeType aType
) {
1682 * If the previous GC created a runnable to finalize objects
1683 * incrementally, and if it hasn't finished yet, finish it now. We
1684 * don't want these to build up. We also don't want to allow any
1685 * existing incremental finalize runnables to run after a
1686 * non-incremental GC, since they are often used to detect leaks.
1688 if (mFinalizeRunnable
) {
1689 mFinalizeRunnable
->ReleaseNow(false);
1690 if (mFinalizeRunnable
) {
1691 // If we re-entered ReleaseNow, we couldn't delete mFinalizeRunnable and
1692 // we need to just continue processing it.
1697 if (mDeferredFinalizerTable
.Count() == 0) {
1702 new IncrementalFinalizeRunnable(this, mDeferredFinalizerTable
);
1704 // Everything should be gone now.
1705 MOZ_ASSERT(mDeferredFinalizerTable
.Count() == 0);
1707 if (aType
== CycleCollectedJSContext::FinalizeIncrementally
) {
1708 NS_DispatchToCurrentThreadQueue(do_AddRef(mFinalizeRunnable
), 2500,
1709 EventQueuePriority::Idle
);
1711 mFinalizeRunnable
->ReleaseNow(false);
1712 MOZ_ASSERT(!mFinalizeRunnable
);
1716 const char* CycleCollectedJSRuntime::OOMStateToString(
1717 const OOMState aOomState
) const {
1718 switch (aOomState
) {
1721 case OOMState::Reporting
:
1723 case OOMState::Reported
:
1725 case OOMState::Recovered
:
1728 MOZ_ASSERT_UNREACHABLE("OOMState holds an invalid value");
1733 void CycleCollectedJSRuntime::AnnotateAndSetOutOfMemory(OOMState
* aStatePtr
,
1734 OOMState aNewState
) {
1735 *aStatePtr
= aNewState
;
1736 CrashReporter::Annotation annotation
=
1737 (aStatePtr
== &mOutOfMemoryState
)
1738 ? CrashReporter::Annotation::JSOutOfMemory
1739 : CrashReporter::Annotation::JSLargeAllocationFailure
;
1741 CrashReporter::AnnotateCrashReport(
1742 annotation
, nsDependentCString(OOMStateToString(aNewState
)));
1745 void CycleCollectedJSRuntime::OnGC(JSContext
* aContext
, JSGCStatus aStatus
,
1746 JS::GCReason aReason
) {
1749 nsCycleCollector_prepareForGarbageCollection();
1750 PrepareWaitingZonesForGC();
1753 if (mOutOfMemoryState
== OOMState::Reported
) {
1754 AnnotateAndSetOutOfMemory(&mOutOfMemoryState
, OOMState::Recovered
);
1756 if (mLargeAllocationFailureState
== OOMState::Reported
) {
1757 AnnotateAndSetOutOfMemory(&mLargeAllocationFailureState
,
1758 OOMState::Recovered
);
1761 // Do any deferred finalization of native objects. We will run the
1762 // finalizer later after we've returned to the event loop if any of
1763 // three conditions hold:
1764 // a) The GC is incremental. In this case, we probably care about pauses.
1765 // b) There is a pending exception. The finalizers are not set up to run
1767 // c) The GC was triggered for internal JS engine reasons. If this is the
1768 // case, then we may be in the middle of running some code that the JIT
1769 // has assumed can't have certain kinds of side effects. Finalizers can do
1770 // all sorts of things, such as run JS, so we want to run them later.
1771 // However, if we're shutting down, we need to destroy things immediately.
1773 // Why do we ever bother finalizing things immediately if that's so
1774 // questionable? In some situations, such as while testing or in low
1775 // memory situations, we really want to free things right away.
1776 bool finalizeIncrementally
= JS::WasIncrementalGC(mJSRuntime
) ||
1777 JS_IsExceptionPending(aContext
) ||
1778 (JS::InternalGCReason(aReason
) &&
1779 aReason
!= JS::GCReason::DESTROY_RUNTIME
);
1781 FinalizeDeferredThings(
1782 finalizeIncrementally
? CycleCollectedJSContext::FinalizeIncrementally
1783 : CycleCollectedJSContext::FinalizeNow
);
1791 CustomGCCallback(aStatus
);
1794 void CycleCollectedJSRuntime::OnOutOfMemory() {
1795 AnnotateAndSetOutOfMemory(&mOutOfMemoryState
, OOMState::Reporting
);
1796 CustomOutOfMemoryCallback();
1797 AnnotateAndSetOutOfMemory(&mOutOfMemoryState
, OOMState::Reported
);
1800 void CycleCollectedJSRuntime::SetLargeAllocationFailure(OOMState aNewState
) {
1801 AnnotateAndSetOutOfMemory(&mLargeAllocationFailureState
, aNewState
);
1804 void CycleCollectedJSRuntime::PrepareWaitingZonesForGC() {
1805 JSContext
* cx
= CycleCollectedJSContext::Get()->Context();
1806 if (mZonesWaitingForGC
.Count() == 0) {
1807 JS::PrepareForFullGC(cx
);
1809 for (auto iter
= mZonesWaitingForGC
.Iter(); !iter
.Done(); iter
.Next()) {
1810 JS::PrepareZoneForGC(cx
, iter
.Get()->GetKey());
1812 mZonesWaitingForGC
.Clear();
1817 void CycleCollectedJSRuntime::OnZoneDestroyed(JSFreeOp
* aFop
, JS::Zone
* aZone
) {
1818 // Remove the zone from the set of zones waiting for GC, if present. This can
1819 // happen if a zone is added to the set during an incremental GC in which it
1820 // is later destroyed.
1821 CycleCollectedJSRuntime
* runtime
= Get();
1822 runtime
->mZonesWaitingForGC
.RemoveEntry(aZone
);
1825 void CycleCollectedJSRuntime::EnvironmentPreparer::invoke(
1826 JS::HandleObject global
, js::ScriptEnvironmentPreparer::Closure
& closure
) {
1827 MOZ_ASSERT(JS_IsGlobalObject(global
));
1828 nsIGlobalObject
* nativeGlobal
= xpc::NativeGlobal(global
);
1830 // Not much we can do if we simply don't have a usable global here...
1831 NS_ENSURE_TRUE_VOID(nativeGlobal
&& nativeGlobal
->HasJSGlobal());
1833 AutoEntryScript
aes(nativeGlobal
, "JS-engine-initiated execution");
1835 MOZ_ASSERT(!JS_IsExceptionPending(aes
.cx()));
1837 DebugOnly
<bool> ok
= closure(aes
.cx());
1839 MOZ_ASSERT_IF(ok
, !JS_IsExceptionPending(aes
.cx()));
1841 // The AutoEntryScript will check for JS_IsExceptionPending on the
1842 // JSContext and report it as needed as it comes off the stack.
1846 CycleCollectedJSRuntime
* CycleCollectedJSRuntime::Get() {
1847 auto context
= CycleCollectedJSContext::Get();
1849 return context
->Runtime();
1854 #ifdef MOZ_JS_DEV_ERROR_INTERCEPTOR
1857 extern void DumpValue(const JS::Value
& val
);
1860 void CycleCollectedJSRuntime::ErrorInterceptor::Shutdown(JSRuntime
* rt
) {
1861 JS_SetErrorInterceptorCallback(rt
, nullptr);
1862 mThrownError
.reset();
1866 void CycleCollectedJSRuntime::ErrorInterceptor::interceptError(
1867 JSContext
* cx
, JS::HandleValue exn
) {
1869 // We already have an error, we don't need anything more.
1873 if (!nsContentUtils::ThreadsafeIsSystemCaller(cx
)) {
1874 // We are only interested in chrome code.
1878 const auto type
= JS_GetErrorType(exn
);
1880 // This is not one of the primitive error types.
1885 case JSExnType::JSEXN_REFERENCEERR
:
1886 case JSExnType::JSEXN_SYNTAXERR
:
1889 // Not one of the errors we are interested in.
1890 // Note that we are not interested in instances of `TypeError`
1891 // for the time being, as DOM (ab)uses this constructor to represent
1892 // all sorts of errors that are not even remotely related to type
1893 // errors (e.g. some network errors).
1894 // If we ever have a mechanism to differentiate between DOM-thrown
1895 // and SpiderMonkey-thrown instances of `TypeError`, we should
1896 // consider watching for `TypeError` here.
1900 // Now copy the details of the exception locally.
1901 // While copying the details of an exception could be expensive, in most runs,
1902 // this will be done at most once during the execution of the process, so the
1903 // total cost should be reasonable.
1905 ErrorDetails details
;
1906 details
.mType
= *type
;
1907 // If `exn` isn't an exception object, `ExtractErrorValues` could end up
1908 // calling `toString()`, which could in turn end up throwing an error. While
1909 // this should work, we want to avoid that complex use case. Fortunately, we
1910 // have already checked above that `exn` is an exception object, so nothing
1911 // such should happen.
1912 nsContentUtils::ExtractErrorValues(cx
, exn
, details
.mFilename
, &details
.mLine
,
1913 &details
.mColumn
, details
.mMessage
);
1915 JS::UniqueChars buf
=
1916 JS::FormatStackDump(cx
, /* showArgs = */ false, /* showLocals = */ false,
1917 /* showThisProps = */ false);
1918 CopyUTF8toUTF16(mozilla::MakeStringSpan(buf
.get()), details
.mStack
);
1920 mThrownError
.emplace(std::move(details
));
1923 void CycleCollectedJSRuntime::ClearRecentDevError() {
1924 mErrorInterceptor
.mThrownError
.reset();
1927 bool CycleCollectedJSRuntime::GetRecentDevError(
1928 JSContext
* cx
, JS::MutableHandle
<JS::Value
> error
) {
1929 if (!mErrorInterceptor
.mThrownError
) {
1933 // Create a copy of the exception.
1934 JS::RootedObject
obj(cx
, JS_NewPlainObject(cx
));
1939 JS::RootedValue
message(cx
);
1940 JS::RootedValue
filename(cx
);
1941 JS::RootedValue
stack(cx
);
1942 if (!ToJSValue(cx
, mErrorInterceptor
.mThrownError
->mMessage
, &message
) ||
1943 !ToJSValue(cx
, mErrorInterceptor
.mThrownError
->mFilename
, &filename
) ||
1944 !ToJSValue(cx
, mErrorInterceptor
.mThrownError
->mStack
, &stack
)) {
1948 // Build the object.
1949 const auto FLAGS
= JSPROP_READONLY
| JSPROP_ENUMERATE
| JSPROP_PERMANENT
;
1950 if (!JS_DefineProperty(cx
, obj
, "message", message
, FLAGS
) ||
1951 !JS_DefineProperty(cx
, obj
, "fileName", filename
, FLAGS
) ||
1952 !JS_DefineProperty(cx
, obj
, "lineNumber",
1953 mErrorInterceptor
.mThrownError
->mLine
, FLAGS
) ||
1954 !JS_DefineProperty(cx
, obj
, "stack", stack
, FLAGS
)) {
1959 error
.setObject(*obj
);
1962 #endif // MOZ_JS_DEV_ERROR_INTERCEPTOR
1964 #undef MOZ_JS_DEV_ERROR_INTERCEPTOR