Bug 1910362 - Create new Nimbus helper r=aaronmt,ohorvath
[gecko.git] / xpcom / base / CycleCollectedJSRuntime.cpp
blobc53e4a8b375c557b9b8844fe54175963711b6035
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:
8 //
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.
30 // Phases:
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
52 // deal with.
53 // To improve debugging, if WantAllTraces() is true all JS objects are
54 // traversed.
56 #include "mozilla/CycleCollectedJSRuntime.h"
58 #include <algorithm>
59 #include <utility>
61 #include "js/Debug.h"
62 #include "js/RealmOptions.h"
63 #include "js/friend/DumpFunctions.h" // js::DumpHeap
64 #include "js/GCAPI.h"
65 #include "js/HeapAPI.h"
66 #include "js/Object.h" // JS::GetClass, JS::GetCompartment, JS::GetPrivate
67 #include "js/PropertyAndElement.h" // JS_DefineProperty
68 #include "js/Warnings.h" // JS::SetWarningReporter
69 #include "js/ShadowRealmCallbacks.h"
70 #include "js/SliceBudget.h"
71 #include "jsfriendapi.h"
72 #include "mozilla/ArrayUtils.h"
73 #include "mozilla/AutoRestore.h"
74 #include "mozilla/CycleCollectedJSContext.h"
75 #include "mozilla/DebuggerOnGCRunnable.h"
76 #include "mozilla/MemoryReporting.h"
77 #include "mozilla/PerfStats.h"
78 #include "mozilla/ProfilerLabels.h"
79 #include "mozilla/ProfilerMarkers.h"
80 #include "mozilla/Sprintf.h"
81 #include "mozilla/StaticPrefs_javascript.h"
82 #include "mozilla/Telemetry.h"
83 #include "mozilla/Unused.h"
84 #include "mozilla/dom/AutoEntryScript.h"
85 #include "mozilla/dom/DOMJSClass.h"
86 #include "mozilla/dom/JSExecutionManager.h"
87 #include "mozilla/dom/Promise.h"
88 #include "mozilla/dom/PromiseBinding.h"
89 #include "mozilla/dom/PromiseDebugging.h"
90 #include "mozilla/dom/ScriptSettings.h"
91 #include "mozilla/dom/ShadowRealmGlobalScope.h"
92 #include "mozilla/dom/RegisterShadowRealmBindings.h"
93 #include "nsContentUtils.h"
94 #include "nsCycleCollectionNoteRootCallback.h"
95 #include "nsCycleCollectionParticipant.h"
96 #include "nsCycleCollector.h"
97 #include "nsDOMJSUtils.h"
98 #include "nsExceptionHandler.h"
99 #include "nsJSUtils.h"
100 #include "nsWrapperCache.h"
101 #include "prenv.h"
103 #if defined(XP_MACOSX)
104 # include "nsMacUtilsImpl.h"
105 #endif
107 #include "nsThread.h"
108 #include "nsThreadUtils.h"
109 #include "xpcpublic.h"
111 #ifdef NIGHTLY_BUILD
112 // For performance reasons, we make the JS Dev Error Interceptor a Nightly-only
113 // feature.
114 # define MOZ_JS_DEV_ERROR_INTERCEPTOR = 1
115 #endif // NIGHTLY_BUILD
117 using namespace mozilla;
118 using namespace mozilla::dom;
120 namespace mozilla {
122 struct DeferredFinalizeFunctionHolder {
123 DeferredFinalizeFunction run;
124 void* data;
127 class IncrementalFinalizeRunnable : public DiscardableRunnable {
128 typedef AutoTArray<DeferredFinalizeFunctionHolder, 16> DeferredFinalizeArray;
129 typedef CycleCollectedJSRuntime::DeferredFinalizerTable
130 DeferredFinalizerTable;
132 CycleCollectedJSRuntime* mRuntime;
133 DeferredFinalizeArray mDeferredFinalizeFunctions;
134 uint32_t mFinalizeFunctionToRun;
135 bool mReleasing;
137 static const PRTime SliceMillis = 5; /* ms */
139 public:
140 IncrementalFinalizeRunnable(CycleCollectedJSRuntime* aRt,
141 DeferredFinalizerTable& aFinalizerTable);
142 virtual ~IncrementalFinalizeRunnable();
144 void ReleaseNow(bool aLimited);
146 NS_DECL_NSIRUNNABLE
149 } // namespace mozilla
151 struct NoteWeakMapChildrenTracer : public JS::CallbackTracer {
152 NoteWeakMapChildrenTracer(JSRuntime* aRt,
153 nsCycleCollectionNoteRootCallback& aCb)
154 : JS::CallbackTracer(aRt, JS::TracerKind::Callback),
155 mCb(aCb),
156 mTracedAny(false),
157 mMap(nullptr),
158 mKey(nullptr),
159 mKeyDelegate(nullptr) {}
160 void onChild(JS::GCCellPtr aThing, const char* name) override;
161 nsCycleCollectionNoteRootCallback& mCb;
162 bool mTracedAny;
163 JSObject* mMap;
164 JS::GCCellPtr mKey;
165 JSObject* mKeyDelegate;
168 void NoteWeakMapChildrenTracer::onChild(JS::GCCellPtr aThing,
169 const char* name) {
170 if (aThing.is<JSString>()) {
171 return;
174 if (!JS::GCThingIsMarkedGrayInCC(aThing) && !mCb.WantAllTraces()) {
175 return;
178 if (JS::IsCCTraceKind(aThing.kind())) {
179 mCb.NoteWeakMapping(mMap, mKey, mKeyDelegate, aThing);
180 mTracedAny = true;
181 } else {
182 JS::TraceChildren(this, aThing);
186 struct NoteWeakMapsTracer : public js::WeakMapTracer {
187 NoteWeakMapsTracer(JSRuntime* aRt, nsCycleCollectionNoteRootCallback& aCccb)
188 : js::WeakMapTracer(aRt), mCb(aCccb), mChildTracer(aRt, aCccb) {}
189 void trace(JSObject* aMap, JS::GCCellPtr aKey, JS::GCCellPtr aValue) override;
190 nsCycleCollectionNoteRootCallback& mCb;
191 NoteWeakMapChildrenTracer mChildTracer;
194 void NoteWeakMapsTracer::trace(JSObject* aMap, JS::GCCellPtr aKey,
195 JS::GCCellPtr aValue) {
196 // If nothing that could be held alive by this entry is marked gray, return.
197 if ((!aKey || !JS::GCThingIsMarkedGrayInCC(aKey)) &&
198 MOZ_LIKELY(!mCb.WantAllTraces())) {
199 if (!aValue || !JS::GCThingIsMarkedGrayInCC(aValue) ||
200 aValue.is<JSString>()) {
201 return;
205 // The cycle collector can only properly reason about weak maps if it can
206 // reason about the liveness of their keys, which in turn requires that
207 // the key can be represented in the cycle collector graph. All existing
208 // uses of weak maps use either objects or scripts as keys, which are okay.
209 MOZ_ASSERT(JS::IsCCTraceKind(aKey.kind()));
211 // As an emergency fallback for non-debug builds, if the key is not
212 // representable in the cycle collector graph, we treat it as marked. This
213 // can cause leaks, but is preferable to ignoring the binding, which could
214 // cause the cycle collector to free live objects.
215 if (!JS::IsCCTraceKind(aKey.kind())) {
216 aKey = nullptr;
219 JSObject* kdelegate = nullptr;
220 if (aKey.is<JSObject>()) {
221 kdelegate = js::UncheckedUnwrapWithoutExpose(&aKey.as<JSObject>());
224 if (JS::IsCCTraceKind(aValue.kind())) {
225 mCb.NoteWeakMapping(aMap, aKey, kdelegate, aValue);
226 } else {
227 mChildTracer.mTracedAny = false;
228 mChildTracer.mMap = aMap;
229 mChildTracer.mKey = aKey;
230 mChildTracer.mKeyDelegate = kdelegate;
232 if (!aValue.is<JSString>()) {
233 JS::TraceChildren(&mChildTracer, aValue);
236 // The delegate could hold alive the key, so report something to the CC
237 // if we haven't already.
238 if (!mChildTracer.mTracedAny && aKey && JS::GCThingIsMarkedGrayInCC(aKey) &&
239 kdelegate) {
240 mCb.NoteWeakMapping(aMap, aKey, kdelegate, nullptr);
245 // Report whether the key or value of a weak mapping entry are gray but need to
246 // be marked black.
247 static void ShouldWeakMappingEntryBeBlack(JSObject* aMap, JS::GCCellPtr aKey,
248 JS::GCCellPtr aValue,
249 bool* aKeyShouldBeBlack,
250 bool* aValueShouldBeBlack) {
251 *aKeyShouldBeBlack = false;
252 *aValueShouldBeBlack = false;
254 // If nothing that could be held alive by this entry is marked gray, return.
255 bool keyMightNeedMarking = aKey && JS::GCThingIsMarkedGrayInCC(aKey);
256 bool valueMightNeedMarking = aValue && JS::GCThingIsMarkedGrayInCC(aValue) &&
257 aValue.kind() != JS::TraceKind::String;
258 if (!keyMightNeedMarking && !valueMightNeedMarking) {
259 return;
262 if (!JS::IsCCTraceKind(aKey.kind())) {
263 aKey = nullptr;
266 if (keyMightNeedMarking && aKey.is<JSObject>()) {
267 JSObject* kdelegate =
268 js::UncheckedUnwrapWithoutExpose(&aKey.as<JSObject>());
269 if (kdelegate && !JS::ObjectIsMarkedGray(kdelegate) &&
270 (!aMap || !JS::ObjectIsMarkedGray(aMap))) {
271 *aKeyShouldBeBlack = true;
275 if (aValue && JS::GCThingIsMarkedGrayInCC(aValue) &&
276 (!aKey || !JS::GCThingIsMarkedGrayInCC(aKey)) &&
277 (!aMap || !JS::ObjectIsMarkedGray(aMap)) &&
278 aValue.kind() != JS::TraceKind::Shape) {
279 *aValueShouldBeBlack = true;
283 struct FixWeakMappingGrayBitsTracer : public js::WeakMapTracer {
284 explicit FixWeakMappingGrayBitsTracer(JSRuntime* aRt)
285 : js::WeakMapTracer(aRt) {}
287 void FixAll() {
288 do {
289 mAnyMarked = false;
290 js::TraceWeakMaps(this);
291 } while (mAnyMarked);
294 void trace(JSObject* aMap, JS::GCCellPtr aKey,
295 JS::GCCellPtr aValue) override {
296 bool keyShouldBeBlack;
297 bool valueShouldBeBlack;
298 ShouldWeakMappingEntryBeBlack(aMap, aKey, aValue, &keyShouldBeBlack,
299 &valueShouldBeBlack);
300 if (keyShouldBeBlack && JS::UnmarkGrayGCThingRecursively(aKey)) {
301 mAnyMarked = true;
304 if (valueShouldBeBlack && JS::UnmarkGrayGCThingRecursively(aValue)) {
305 mAnyMarked = true;
309 MOZ_INIT_OUTSIDE_CTOR bool mAnyMarked;
312 #ifdef DEBUG
313 // Check whether weak maps are marked correctly according to the logic above.
314 struct CheckWeakMappingGrayBitsTracer : public js::WeakMapTracer {
315 explicit CheckWeakMappingGrayBitsTracer(JSRuntime* aRt)
316 : js::WeakMapTracer(aRt), mFailed(false) {}
318 static bool Check(JSRuntime* aRt) {
319 CheckWeakMappingGrayBitsTracer tracer(aRt);
320 js::TraceWeakMaps(&tracer);
321 return !tracer.mFailed;
324 void trace(JSObject* aMap, JS::GCCellPtr aKey,
325 JS::GCCellPtr aValue) override {
326 bool keyShouldBeBlack;
327 bool valueShouldBeBlack;
328 ShouldWeakMappingEntryBeBlack(aMap, aKey, aValue, &keyShouldBeBlack,
329 &valueShouldBeBlack);
331 if (keyShouldBeBlack) {
332 fprintf(stderr, "Weak mapping key %p of map %p should be black\n",
333 aKey.asCell(), aMap);
334 mFailed = true;
337 if (valueShouldBeBlack) {
338 fprintf(stderr, "Weak mapping value %p of map %p should be black\n",
339 aValue.asCell(), aMap);
340 mFailed = true;
344 bool mFailed;
346 #endif // DEBUG
348 static void CheckParticipatesInCycleCollection(JS::GCCellPtr aThing,
349 const char* aName,
350 void* aClosure) {
351 bool* cycleCollectionEnabled = static_cast<bool*>(aClosure);
353 if (*cycleCollectionEnabled) {
354 return;
357 if (JS::IsCCTraceKind(aThing.kind()) && JS::GCThingIsMarkedGrayInCC(aThing)) {
358 *cycleCollectionEnabled = true;
362 NS_IMETHODIMP
363 JSGCThingParticipant::TraverseNative(void* aPtr,
364 nsCycleCollectionTraversalCallback& aCb) {
365 auto runtime = reinterpret_cast<CycleCollectedJSRuntime*>(
366 reinterpret_cast<char*>(this) -
367 offsetof(CycleCollectedJSRuntime, mGCThingCycleCollectorGlobal));
369 JS::GCCellPtr cellPtr(aPtr, JS::GCThingTraceKind(aPtr));
370 runtime->TraverseGCThing(CycleCollectedJSRuntime::TRAVERSE_FULL, cellPtr,
371 aCb);
372 return NS_OK;
375 // NB: This is only used to initialize the participant in
376 // CycleCollectedJSRuntime. It should never be used directly.
377 static JSGCThingParticipant sGCThingCycleCollectorGlobal;
379 NS_IMETHODIMP
380 JSZoneParticipant::TraverseNative(void* aPtr,
381 nsCycleCollectionTraversalCallback& aCb) {
382 auto runtime = reinterpret_cast<CycleCollectedJSRuntime*>(
383 reinterpret_cast<char*>(this) -
384 offsetof(CycleCollectedJSRuntime, mJSZoneCycleCollectorGlobal));
386 MOZ_ASSERT(!aCb.WantAllTraces());
387 JS::Zone* zone = static_cast<JS::Zone*>(aPtr);
389 runtime->TraverseZone(zone, aCb);
390 return NS_OK;
393 struct TraversalTracer : public JS::CallbackTracer {
394 TraversalTracer(JSRuntime* aRt, nsCycleCollectionTraversalCallback& aCb)
395 : JS::CallbackTracer(aRt, JS::TracerKind::Callback,
396 JS::TraceOptions(JS::WeakMapTraceAction::Skip,
397 JS::WeakEdgeTraceAction::Trace)),
398 mCb(aCb) {}
399 void onChild(JS::GCCellPtr aThing, const char* name) override;
400 nsCycleCollectionTraversalCallback& mCb;
403 void TraversalTracer::onChild(JS::GCCellPtr aThing, const char* name) {
404 // Checking strings and symbols for being gray is rather slow, and we don't
405 // need either of them for the cycle collector.
406 if (aThing.is<JSString>() || aThing.is<JS::Symbol>()) {
407 return;
410 // Don't traverse non-gray objects, unless we want all traces.
411 if (!JS::GCThingIsMarkedGrayInCC(aThing) && !mCb.WantAllTraces()) {
412 return;
416 * This function needs to be careful to avoid stack overflow. Normally, when
417 * IsCCTraceKind is true, the recursion terminates immediately as we just add
418 * |thing| to the CC graph. So overflow is only possible when there are long
419 * or cyclic chains of non-IsCCTraceKind GC things. Places where this can
420 * occur use special APIs to handle such chains iteratively.
422 if (JS::IsCCTraceKind(aThing.kind())) {
423 if (MOZ_UNLIKELY(mCb.WantDebugInfo())) {
424 char buffer[200];
425 context().getEdgeName(name, buffer, sizeof(buffer));
426 mCb.NoteNextEdgeName(buffer);
428 mCb.NoteJSChild(aThing);
429 return;
432 // Allow re-use of this tracer inside trace callback.
433 JS::AutoClearTracingContext actc(this);
435 if (aThing.is<js::Shape>()) {
436 // The maximum depth of traversal when tracing a Shape is unbounded, due to
437 // the parent pointers on the shape.
438 JS_TraceShapeCycleCollectorChildren(this, aThing);
439 } else {
440 JS::TraceChildren(this, aThing);
445 * The cycle collection participant for a Zone is intended to produce the same
446 * results as if all of the gray GCthings in a zone were merged into a single
447 * node, except for self-edges. This avoids the overhead of representing all of
448 * the GCthings in the zone in the cycle collector graph, which should be much
449 * faster if many of the GCthings in the zone are gray.
451 * Zone merging should not always be used, because it is a conservative
452 * approximation of the true cycle collector graph that can incorrectly identify
453 * some garbage objects as being live. For instance, consider two cycles that
454 * pass through a zone, where one is garbage and the other is live. If we merge
455 * the entire zone, the cycle collector will think that both are alive.
457 * We don't have to worry about losing track of a garbage cycle, because any
458 * such garbage cycle incorrectly identified as live must contain at least one
459 * C++ to JS edge, and XPConnect will always add the C++ object to the CC graph.
460 * (This is in contrast to pure C++ garbage cycles, which must always be
461 * properly identified, because we clear the purple buffer during every CC,
462 * which may contain the last reference to a garbage cycle.)
465 // NB: This is only used to initialize the participant in
466 // CycleCollectedJSRuntime. It should never be used directly.
467 static const JSZoneParticipant sJSZoneCycleCollectorGlobal;
469 static void JSObjectsTenuredCb(JS::GCContext* aGCContext, void* aData) {
470 static_cast<CycleCollectedJSRuntime*>(aData)->JSObjectsTenured(aGCContext);
473 static void MozCrashWarningReporter(JSContext*, JSErrorReport*) {
474 MOZ_CRASH("Why is someone touching JSAPI without an AutoJSAPI?");
477 JSHolderMap::Entry::Entry() : Entry(nullptr, nullptr, nullptr) {}
479 JSHolderMap::Entry::Entry(void* aHolder, nsScriptObjectTracer* aTracer,
480 JS::Zone* aZone)
481 : mHolder(aHolder),
482 mTracer(aTracer)
483 #ifdef DEBUG
485 mZone(aZone)
486 #endif
490 void JSHolderMap::EntryVectorIter::Settle() {
491 if (Done()) {
492 return;
495 Entry* entry = &mIter.Get();
497 // If the entry has been cleared, remove it and shrink the vector.
498 if (!entry->mHolder && !mHolderMap.RemoveEntry(mVector, entry)) {
499 // We removed the last entry, so reset the iterator to an empty one.
500 mIter = EntryVector().Iter();
501 MOZ_ASSERT(Done());
505 JSHolderMap::Iter::Iter(JSHolderMap& aMap, WhichHolders aWhich)
506 : mHolderMap(aMap), mIter(aMap, aMap.mAnyZoneJSHolders) {
507 MOZ_RELEASE_ASSERT(!mHolderMap.mHasIterator);
508 mHolderMap.mHasIterator = true;
510 // Populate vector of zones to iterate after the any-zone holders.
511 for (auto i = aMap.mPerZoneJSHolders.iter(); !i.done(); i.next()) {
512 JS::Zone* zone = i.get().key();
513 if (aWhich == AllHolders || JS::NeedGrayRootsForZone(i.get().key())) {
514 MOZ_ALWAYS_TRUE(mZones.append(zone));
518 Settle();
521 void JSHolderMap::Iter::Settle() {
522 while (mIter.Done()) {
523 if (mZone && mIter.Vector().IsEmpty()) {
524 mHolderMap.mPerZoneJSHolders.remove(mZone);
527 mZone = nullptr;
528 if (mZones.empty()) {
529 break;
532 mZone = mZones.popCopy();
533 EntryVector& vector = *mHolderMap.mPerZoneJSHolders.lookup(mZone)->value();
534 new (&mIter) EntryVectorIter(mHolderMap, vector);
538 void JSHolderMap::Iter::UpdateForRemovals() {
539 mIter.Settle();
540 Settle();
543 JSHolderMap::JSHolderMap() : mJSHolderMap(256) {}
545 bool JSHolderMap::RemoveEntry(EntryVector& aJSHolders, Entry* aEntry) {
546 MOZ_ASSERT(aEntry);
547 MOZ_ASSERT(!aEntry->mHolder);
549 // Remove all dead entries from the end of the vector.
550 while (!aJSHolders.GetLast().mHolder && &aJSHolders.GetLast() != aEntry) {
551 aJSHolders.PopLast();
554 // Swap the element we want to remove with the last one and update the hash
555 // table.
556 Entry* lastEntry = &aJSHolders.GetLast();
557 if (aEntry != lastEntry) {
558 MOZ_ASSERT(lastEntry->mHolder);
559 *aEntry = *lastEntry;
560 MOZ_ASSERT(mJSHolderMap.has(aEntry->mHolder));
561 MOZ_ALWAYS_TRUE(mJSHolderMap.put(aEntry->mHolder, aEntry));
564 aJSHolders.PopLast();
566 // Return whether aEntry is still in the vector.
567 return aEntry != lastEntry;
570 bool JSHolderMap::Has(void* aHolder) const { return mJSHolderMap.has(aHolder); }
572 nsScriptObjectTracer* JSHolderMap::Get(void* aHolder) const {
573 auto ptr = mJSHolderMap.lookup(aHolder);
574 if (!ptr) {
575 return nullptr;
578 Entry* entry = ptr->value();
579 MOZ_ASSERT(entry->mHolder == aHolder);
580 return entry->mTracer;
583 nsScriptObjectTracer* JSHolderMap::Extract(void* aHolder) {
584 MOZ_ASSERT(aHolder);
586 auto ptr = mJSHolderMap.lookup(aHolder);
587 if (!ptr) {
588 return nullptr;
591 Entry* entry = ptr->value();
592 MOZ_ASSERT(entry->mHolder == aHolder);
593 nsScriptObjectTracer* tracer = entry->mTracer;
595 // Clear the entry's contents. It will be removed the next time iteration
596 // visits this entry.
597 *entry = Entry();
599 mJSHolderMap.remove(ptr);
601 return tracer;
604 void JSHolderMap::Put(void* aHolder, nsScriptObjectTracer* aTracer,
605 JS::Zone* aZone) {
606 MOZ_ASSERT(aHolder);
607 MOZ_ASSERT(aTracer);
609 // Don't associate multi-zone holders with a zone, even if one is supplied.
610 if (!aTracer->IsSingleZoneJSHolder()) {
611 aZone = nullptr;
614 auto ptr = mJSHolderMap.lookupForAdd(aHolder);
615 if (ptr) {
616 Entry* entry = ptr->value();
617 #ifdef DEBUG
618 MOZ_ASSERT(entry->mHolder == aHolder);
619 MOZ_ASSERT(entry->mTracer == aTracer,
620 "Don't call HoldJSObjects in superclass ctors");
621 if (aZone) {
622 if (entry->mZone) {
623 MOZ_ASSERT(entry->mZone == aZone);
624 } else {
625 entry->mZone = aZone;
628 #endif
629 entry->mTracer = aTracer;
630 return;
633 EntryVector* vector = &mAnyZoneJSHolders;
634 if (aZone) {
635 auto ptr = mPerZoneJSHolders.lookupForAdd(aZone);
636 if (!ptr) {
637 MOZ_ALWAYS_TRUE(
638 mPerZoneJSHolders.add(ptr, aZone, MakeUnique<EntryVector>()));
640 vector = ptr->value().get();
643 vector->InfallibleAppend(Entry{aHolder, aTracer, aZone});
644 MOZ_ALWAYS_TRUE(mJSHolderMap.add(ptr, aHolder, &vector->GetLast()));
647 size_t JSHolderMap::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
648 size_t n = 0;
650 // We're deliberately not measuring anything hanging off the entries in
651 // mJSHolders.
652 n += mJSHolderMap.shallowSizeOfExcludingThis(aMallocSizeOf);
653 n += mAnyZoneJSHolders.SizeOfExcludingThis(aMallocSizeOf);
654 n += mPerZoneJSHolders.shallowSizeOfExcludingThis(aMallocSizeOf);
655 for (auto i = mPerZoneJSHolders.iter(); !i.done(); i.next()) {
656 n += i.get().value()->SizeOfExcludingThis(aMallocSizeOf);
659 return n;
662 static bool InitializeShadowRealm(JSContext* aCx,
663 JS::Handle<JSObject*> aGlobal) {
664 MOZ_ASSERT(StaticPrefs::javascript_options_experimental_shadow_realms());
666 JSAutoRealm ar(aCx, aGlobal);
667 return dom::RegisterShadowRealmBindings(aCx, aGlobal);
670 CycleCollectedJSRuntime::CycleCollectedJSRuntime(JSContext* aCx)
671 : mContext(nullptr),
672 mGCThingCycleCollectorGlobal(sGCThingCycleCollectorGlobal),
673 mJSZoneCycleCollectorGlobal(sJSZoneCycleCollectorGlobal),
674 mJSRuntime(JS_GetRuntime(aCx)),
675 mHasPendingIdleGCTask(false),
676 mPrevGCSliceCallback(nullptr),
677 mOutOfMemoryState(OOMState::OK),
678 mLargeAllocationFailureState(OOMState::OK)
679 #ifdef DEBUG
681 mShutdownCalled(false)
682 #endif
684 MOZ_COUNT_CTOR(CycleCollectedJSRuntime);
685 MOZ_ASSERT(aCx);
686 MOZ_ASSERT(mJSRuntime);
688 #if defined(XP_MACOSX)
689 if (!XRE_IsParentProcess()) {
690 nsMacUtilsImpl::EnableTCSMIfAvailable();
692 #endif
694 if (!JS_AddExtraGCRootsTracer(aCx, TraceBlackJS, this)) {
695 MOZ_CRASH("JS_AddExtraGCRootsTracer failed");
697 JS_SetGrayGCRootsTracer(aCx, TraceGrayJS, this);
698 JS_SetGCCallback(aCx, GCCallback, this);
699 mPrevGCSliceCallback = JS::SetGCSliceCallback(aCx, GCSliceCallback);
701 JS::AddGCNurseryCollectionCallback(aCx, GCNurseryCollectionCallback, this);
703 JS_SetObjectsTenuredCallback(aCx, JSObjectsTenuredCb, this);
704 JS::SetOutOfMemoryCallback(aCx, OutOfMemoryCallback, this);
705 JS::SetWaitCallback(mJSRuntime, BeforeWaitCallback, AfterWaitCallback,
706 sizeof(dom::AutoYieldJSThreadExecution));
707 JS::SetWarningReporter(aCx, MozCrashWarningReporter);
708 JS::SetShadowRealmInitializeGlobalCallback(aCx, InitializeShadowRealm);
709 JS::SetShadowRealmGlobalCreationCallback(aCx, dom::NewShadowRealmGlobal);
711 js::AutoEnterOOMUnsafeRegion::setAnnotateOOMAllocationSizeCallback(
712 CrashReporter::AnnotateOOMAllocationSize);
714 static js::DOMCallbacks DOMcallbacks = {InstanceClassHasProtoAtDepth};
715 SetDOMCallbacks(aCx, &DOMcallbacks);
716 js::SetScriptEnvironmentPreparer(aCx, &mEnvironmentPreparer);
718 JS::dbg::SetDebuggerMallocSizeOf(aCx, moz_malloc_size_of);
720 #ifdef MOZ_JS_DEV_ERROR_INTERCEPTOR
721 JS_SetErrorInterceptorCallback(mJSRuntime, &mErrorInterceptor);
722 #endif // MOZ_JS_DEV_ERROR_INTERCEPTOR
724 JS_SetDestroyZoneCallback(aCx, OnZoneDestroyed);
727 #ifdef NS_BUILD_REFCNT_LOGGING
728 class JSLeakTracer : public JS::CallbackTracer {
729 public:
730 explicit JSLeakTracer(JSRuntime* aRuntime)
731 : JS::CallbackTracer(aRuntime, JS::TracerKind::Callback,
732 JS::WeakMapTraceAction::TraceKeysAndValues) {}
734 private:
735 void onChild(JS::GCCellPtr thing, const char* name) override {
736 const char* kindName = JS::GCTraceKindToAscii(thing.kind());
737 size_t size = JS::GCTraceKindSize(thing.kind());
738 MOZ_LOG_CTOR(thing.asCell(), kindName, size);
741 #endif
743 void CycleCollectedJSRuntime::Shutdown(JSContext* aCx) {
744 #ifdef MOZ_JS_DEV_ERROR_INTERCEPTOR
745 mErrorInterceptor.Shutdown(mJSRuntime);
746 #endif // MOZ_JS_DEV_ERROR_INTERCEPTOR
748 // There should not be any roots left to trace at this point. Ensure any that
749 // remain are flagged as leaks.
750 #ifdef NS_BUILD_REFCNT_LOGGING
751 JSLeakTracer tracer(Runtime());
752 TraceNativeBlackRoots(&tracer);
753 TraceAllNativeGrayRoots(&tracer);
754 #endif
756 #ifdef DEBUG
757 mShutdownCalled = true;
758 #endif
760 JS_SetDestroyZoneCallback(aCx, nullptr);
762 JS::RemoveGCNurseryCollectionCallback(aCx, GCNurseryCollectionCallback, this);
765 CycleCollectedJSRuntime::~CycleCollectedJSRuntime() {
766 MOZ_COUNT_DTOR(CycleCollectedJSRuntime);
767 MOZ_ASSERT(!mDeferredFinalizerTable.Count());
768 MOZ_ASSERT(!mFinalizeRunnable);
769 MOZ_ASSERT(mShutdownCalled);
772 void CycleCollectedJSRuntime::SetContext(CycleCollectedJSContext* aContext) {
773 MOZ_ASSERT(!mContext || !aContext, "Don't replace the context!");
774 mContext = aContext;
777 size_t CycleCollectedJSRuntime::SizeOfExcludingThis(
778 MallocSizeOf aMallocSizeOf) const {
779 return mJSHolders.SizeOfExcludingThis(aMallocSizeOf);
782 void CycleCollectedJSRuntime::UnmarkSkippableJSHolders() {
783 for (JSHolderMap::Iter entry(mJSHolders); !entry.Done(); entry.Next()) {
784 entry->mTracer->CanSkip(entry->mHolder, true);
788 void CycleCollectedJSRuntime::DescribeGCThing(
789 bool aIsMarked, JS::GCCellPtr aThing,
790 nsCycleCollectionTraversalCallback& aCb) const {
791 if (!aCb.WantDebugInfo()) {
792 aCb.DescribeGCedNode(aIsMarked, "JS Object");
793 return;
796 char name[72];
797 uint64_t compartmentAddress = 0;
798 if (aThing.is<JSObject>()) {
799 JSObject* obj = &aThing.as<JSObject>();
800 compartmentAddress = (uint64_t)JS::GetCompartment(obj);
801 const JSClass* clasp = JS::GetClass(obj);
803 // Give the subclass a chance to do something
804 if (DescribeCustomObjects(obj, clasp, name)) {
805 // Nothing else to do!
806 } else if (js::IsFunctionObject(obj)) {
807 JSFunction* fun = JS_GetObjectFunction(obj);
808 JSString* str = JS_GetMaybePartialFunctionDisplayId(fun);
809 if (str) {
810 JSLinearString* linear = JS_ASSERT_STRING_IS_LINEAR(str);
811 nsAutoString chars;
812 AssignJSLinearString(chars, linear);
813 NS_ConvertUTF16toUTF8 fname(chars);
814 SprintfLiteral(name, "JS Object (Function - %s)", fname.get());
815 } else {
816 SprintfLiteral(name, "JS Object (Function)");
818 } else {
819 SprintfLiteral(name, "JS Object (%s)", clasp->name);
821 } else {
822 SprintfLiteral(name, "%s", JS::GCTraceKindToAscii(aThing.kind()));
825 // Disable printing global for objects while we figure out ObjShrink fallout.
826 aCb.DescribeGCedNode(aIsMarked, name, compartmentAddress);
829 void CycleCollectedJSRuntime::NoteGCThingJSChildren(
830 JS::GCCellPtr aThing, nsCycleCollectionTraversalCallback& aCb) const {
831 TraversalTracer trc(mJSRuntime, aCb);
832 JS::TraceChildren(&trc, aThing);
835 void CycleCollectedJSRuntime::NoteGCThingXPCOMChildren(
836 const JSClass* aClasp, JSObject* aObj,
837 nsCycleCollectionTraversalCallback& aCb) const {
838 MOZ_ASSERT(aClasp);
839 MOZ_ASSERT(aClasp == JS::GetClass(aObj));
841 JS::Rooted<JSObject*> obj(RootingCx(), aObj);
843 if (NoteCustomGCThingXPCOMChildren(aClasp, obj, aCb)) {
844 // Nothing else to do!
845 return;
848 // XXX This test does seem fragile, we should probably allowlist classes
849 // that do hold a strong reference, but that might not be possible.
850 if (aClasp->slot0IsISupports()) {
851 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "JS::GetObjectISupports(obj)");
852 aCb.NoteXPCOMChild(JS::GetObjectISupports<nsISupports>(obj));
853 return;
856 const DOMJSClass* domClass = GetDOMClass(aClasp);
857 if (domClass) {
858 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "UnwrapDOMObject(obj)");
859 // It's possible that our object is an unforgeable holder object, in
860 // which case it doesn't actually have a C++ DOM object associated with
861 // it. Use UnwrapPossiblyNotInitializedDOMObject, which produces null in
862 // that case, since NoteXPCOMChild/NoteNativeChild are null-safe.
863 if (domClass->mDOMObjectIsISupports) {
864 aCb.NoteXPCOMChild(
865 UnwrapPossiblyNotInitializedDOMObject<nsISupports>(obj));
866 } else if (domClass->mParticipant) {
867 aCb.NoteNativeChild(UnwrapPossiblyNotInitializedDOMObject<void>(obj),
868 domClass->mParticipant);
870 return;
873 if (IsRemoteObjectProxy(obj)) {
874 auto handler =
875 static_cast<const RemoteObjectProxyBase*>(js::GetProxyHandler(obj));
876 return handler->NoteChildren(obj, aCb);
879 JS::Value value = js::MaybeGetScriptPrivate(obj);
880 if (!value.isUndefined()) {
881 aCb.NoteXPCOMChild(static_cast<nsISupports*>(value.toPrivate()));
885 void CycleCollectedJSRuntime::TraverseGCThing(
886 TraverseSelect aTs, JS::GCCellPtr aThing,
887 nsCycleCollectionTraversalCallback& aCb) {
888 bool isMarkedGray = JS::GCThingIsMarkedGrayInCC(aThing);
890 if (aTs == TRAVERSE_FULL) {
891 DescribeGCThing(!isMarkedGray, aThing, aCb);
894 // If this object is alive, then all of its children are alive. For JS
895 // objects, the black-gray invariant ensures the children are also marked
896 // black. For C++ objects, the ref count from this object will keep them
897 // alive. Thus we don't need to trace our children, unless we are debugging
898 // using WantAllTraces.
899 if (!isMarkedGray && !aCb.WantAllTraces()) {
900 return;
903 if (aTs == TRAVERSE_FULL) {
904 NoteGCThingJSChildren(aThing, aCb);
907 if (aThing.is<JSObject>()) {
908 JSObject* obj = &aThing.as<JSObject>();
909 NoteGCThingXPCOMChildren(JS::GetClass(obj), obj, aCb);
913 struct TraverseObjectShimClosure {
914 nsCycleCollectionTraversalCallback& cb;
915 CycleCollectedJSRuntime* self;
918 void CycleCollectedJSRuntime::TraverseZone(
919 JS::Zone* aZone, nsCycleCollectionTraversalCallback& aCb) {
921 * We treat the zone as being gray. We handle non-gray GCthings in the
922 * zone by not reporting their children to the CC. The black-gray invariant
923 * ensures that any JS children will also be non-gray, and thus don't need to
924 * be added to the graph. For C++ children, not representing the edge from the
925 * non-gray JS GCthings to the C++ object will keep the child alive.
927 * We don't allow zone merging in a WantAllTraces CC, because then these
928 * assumptions don't hold.
930 aCb.DescribeGCedNode(false, "JS Zone");
933 * Every JS child of everything in the zone is either in the zone
934 * or is a cross-compartment wrapper. In the former case, we don't need to
935 * represent these edges in the CC graph because JS objects are not ref
936 * counted. In the latter case, the JS engine keeps a map of these wrappers,
937 * which we iterate over. Edges between compartments in the same zone will add
938 * unnecessary loop edges to the graph (bug 842137).
940 TraversalTracer trc(mJSRuntime, aCb);
941 js::TraceGrayWrapperTargets(&trc, aZone);
944 * To find C++ children of things in the zone, we scan every JS Object in
945 * the zone. Only JS Objects can have C++ children.
947 TraverseObjectShimClosure closure = {aCb, this};
948 js::IterateGrayObjects(aZone, TraverseObjectShim, &closure);
951 /* static */
952 void CycleCollectedJSRuntime::TraverseObjectShim(
953 void* aData, JS::GCCellPtr aThing, const JS::AutoRequireNoGC& nogc) {
954 TraverseObjectShimClosure* closure =
955 static_cast<TraverseObjectShimClosure*>(aData);
957 MOZ_ASSERT(aThing.is<JSObject>());
958 closure->self->TraverseGCThing(CycleCollectedJSRuntime::TRAVERSE_CPP, aThing,
959 closure->cb);
962 void CycleCollectedJSRuntime::TraverseNativeRoots(
963 nsCycleCollectionNoteRootCallback& aCb) {
964 // NB: This is here just to preserve the existing XPConnect order. I doubt it
965 // would hurt to do this after the JS holders.
966 TraverseAdditionalNativeRoots(aCb);
968 for (JSHolderMap::Iter entry(mJSHolders); !entry.Done(); entry.Next()) {
969 void* holder = entry->mHolder;
970 nsScriptObjectTracer* tracer = entry->mTracer;
972 bool noteRoot = false;
973 if (MOZ_UNLIKELY(aCb.WantAllTraces())) {
974 noteRoot = true;
975 } else {
976 tracer->Trace(holder,
977 TraceCallbackFunc(CheckParticipatesInCycleCollection),
978 &noteRoot);
981 if (noteRoot) {
982 aCb.NoteNativeRoot(holder, tracer);
987 /* static */
988 void CycleCollectedJSRuntime::TraceBlackJS(JSTracer* aTracer, void* aData) {
989 CycleCollectedJSRuntime* self = static_cast<CycleCollectedJSRuntime*>(aData);
991 self->TraceNativeBlackRoots(aTracer);
994 /* static */
995 bool CycleCollectedJSRuntime::TraceGrayJS(JSTracer* aTracer,
996 JS::SliceBudget& budget,
997 void* aData) {
998 CycleCollectedJSRuntime* self = static_cast<CycleCollectedJSRuntime*>(aData);
1000 // Mark these roots as gray so the CC can walk them later.
1002 JSHolderMap::WhichHolders which = JSHolderMap::AllHolders;
1004 // Only trace holders in collecting zones when marking, except if we are
1005 // collecting the atoms zone since any holder may point into that zone.
1006 if (aTracer->isMarkingTracer() &&
1007 !JS::AtomsZoneIsCollecting(self->Runtime())) {
1008 which = JSHolderMap::HoldersRequiredForGrayMarking;
1011 return self->TraceNativeGrayRoots(aTracer, which, budget);
1014 /* static */
1015 void CycleCollectedJSRuntime::GCCallback(JSContext* aContext,
1016 JSGCStatus aStatus,
1017 JS::GCReason aReason, void* aData) {
1018 CycleCollectedJSRuntime* self = static_cast<CycleCollectedJSRuntime*>(aData);
1020 MOZ_ASSERT(CycleCollectedJSContext::Get()->Context() == aContext);
1021 MOZ_ASSERT(CycleCollectedJSContext::Get()->Runtime() == self);
1023 self->OnGC(aContext, aStatus, aReason);
1026 struct GCMajorMarker : public BaseMarkerType<GCMajorMarker> {
1027 static constexpr const char* Name = "GCMajor";
1028 static constexpr const char* Description =
1029 "Summary data for an entire major GC, encompassing a set of "
1030 "incremental slices. The main thread is not blocked for the "
1031 "entire major GC interval, only for the individual slices.";
1033 using MS = MarkerSchema;
1034 static constexpr MS::PayloadField PayloadFields[] = {
1035 {"timings", MS::InputType::CString, "GC timings"}};
1037 static constexpr MS::Location Locations[] = {MS::Location::MarkerChart,
1038 MS::Location::MarkerTable,
1039 MS::Location::TimelineMemory};
1040 static constexpr MS::ETWMarkerGroup Group = MS::ETWMarkerGroup::Memory;
1042 static void StreamJSONMarkerData(
1043 mozilla::baseprofiler::SpliceableJSONWriter& aWriter,
1044 const mozilla::ProfilerString8View& aTimingJSON) {
1045 if (aTimingJSON.Length() != 0) {
1046 aWriter.SplicedJSONProperty("timings", aTimingJSON);
1047 } else {
1048 aWriter.NullProperty("timings");
1053 /* static */
1054 void CycleCollectedJSRuntime::GCSliceCallback(JSContext* aContext,
1055 JS::GCProgress aProgress,
1056 const JS::GCDescription& aDesc) {
1057 CycleCollectedJSRuntime* self = CycleCollectedJSRuntime::Get();
1058 MOZ_ASSERT(CycleCollectedJSContext::Get()->Context() == aContext);
1060 if (profiler_thread_is_being_profiled_for_markers()) {
1061 if (aProgress == JS::GC_CYCLE_END) {
1062 profiler_add_marker("GCMajor", baseprofiler::category::GCCC,
1063 MarkerTiming::Interval(aDesc.startTime(aContext),
1064 aDesc.endTime(aContext)),
1065 GCMajorMarker{},
1066 ProfilerString8View::WrapNullTerminatedString(
1067 aDesc.formatJSONProfiler(aContext).get()));
1068 } else if (aProgress == JS::GC_SLICE_END) {
1069 struct GCSliceMarker {
1070 static constexpr mozilla::Span<const char> MarkerTypeName() {
1071 return mozilla::MakeStringSpan("GCSlice");
1073 static void StreamJSONMarkerData(
1074 mozilla::baseprofiler::SpliceableJSONWriter& aWriter,
1075 const mozilla::ProfilerString8View& aTimingJSON) {
1076 if (aTimingJSON.Length() != 0) {
1077 aWriter.SplicedJSONProperty("timings", aTimingJSON);
1078 } else {
1079 aWriter.NullProperty("timings");
1082 static mozilla::MarkerSchema MarkerTypeDisplay() {
1083 using MS = mozilla::MarkerSchema;
1084 MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable,
1085 MS::Location::TimelineMemory};
1086 schema.AddStaticLabelValue(
1087 "Description",
1088 "One slice of an incremental garbage collection (GC). The main "
1089 "thread is blocked during this time.");
1090 // No display instructions here, there is special handling in the
1091 // front-end.
1092 return schema;
1096 profiler_add_marker("GCSlice", baseprofiler::category::GCCC,
1097 MarkerTiming::Interval(aDesc.lastSliceStart(aContext),
1098 aDesc.lastSliceEnd(aContext)),
1099 GCSliceMarker{},
1100 ProfilerString8View::WrapNullTerminatedString(
1101 aDesc.sliceToJSONProfiler(aContext).get()));
1105 if (aProgress == JS::GC_CYCLE_END &&
1106 JS::dbg::FireOnGarbageCollectionHookRequired(aContext)) {
1107 JS::GCReason reason = aDesc.reason_;
1108 Unused << NS_WARN_IF(
1109 NS_FAILED(DebuggerOnGCRunnable::Enqueue(aContext, aDesc)) &&
1110 reason != JS::GCReason::SHUTDOWN_CC &&
1111 reason != JS::GCReason::DESTROY_RUNTIME &&
1112 reason != JS::GCReason::XPCONNECT_SHUTDOWN);
1115 if (self->mPrevGCSliceCallback) {
1116 self->mPrevGCSliceCallback(aContext, aProgress, aDesc);
1120 /* static */
1121 void CycleCollectedJSRuntime::GCNurseryCollectionCallback(
1122 JSContext* aContext, JS::GCNurseryProgress aProgress, JS::GCReason aReason,
1123 void* data) {
1124 CycleCollectedJSRuntime* self = static_cast<CycleCollectedJSRuntime*>(data);
1125 MOZ_ASSERT(self->GetContext()->Context() == aContext);
1127 TimeStamp now = TimeStamp::Now();
1128 if (aProgress == JS::GCNurseryProgress::GC_NURSERY_COLLECTION_START) {
1129 self->mLatestNurseryCollectionStart = now;
1130 } else if (aProgress == JS::GCNurseryProgress::GC_NURSERY_COLLECTION_END) {
1131 PerfStats::RecordMeasurement(PerfStats::Metric::MinorGC,
1132 now - self->mLatestNurseryCollectionStart);
1135 if (aProgress == JS::GCNurseryProgress::GC_NURSERY_COLLECTION_END &&
1136 profiler_thread_is_being_profiled_for_markers()) {
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);
1146 } else {
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 schema.AddStaticLabelValue(
1155 "Description",
1156 "A minor GC (aka nursery collection) to clear out the buffer used "
1157 "for recent allocations and move surviving data to the tenured "
1158 "(long-lived) heap.");
1159 // No display instructions here, there is special handling in the
1160 // front-end.
1161 return schema;
1165 profiler_add_marker(
1166 "GCMinor", baseprofiler::category::GCCC,
1167 MarkerTiming::Interval(self->mLatestNurseryCollectionStart, now),
1168 GCMinorMarker{},
1169 ProfilerString8View::WrapNullTerminatedString(
1170 JS::MinorGcToJSON(aContext).get()));
1174 /* static */
1175 void CycleCollectedJSRuntime::OutOfMemoryCallback(JSContext* aContext,
1176 void* aData) {
1177 CycleCollectedJSRuntime* self = static_cast<CycleCollectedJSRuntime*>(aData);
1179 MOZ_ASSERT(CycleCollectedJSContext::Get()->Context() == aContext);
1180 MOZ_ASSERT(CycleCollectedJSContext::Get()->Runtime() == self);
1182 self->OnOutOfMemory();
1185 /* static */
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;
1194 /* static */
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
1244 #endif
1246 #ifdef CHECK_SINGLE_ZONE_JS_HOLDERS
1248 // A tracer that checks that a JS holder only holds JS GC things in a single
1249 // JS::Zone.
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 {
1258 if (JS::IsAtomsZone(aZone)) {
1259 // Any holder may contain pointers into the atoms zone.
1260 return;
1263 if (!mZone) {
1264 mZone = aZone;
1265 return;
1268 if (aZone == mZone) {
1269 return;
1272 // Most JS holders only contain pointers to GC things in a single zone. We
1273 // group holders by referent zone where possible, allowing us to improve GC
1274 // performance by only tracing holders for zones that are being collected.
1276 // Additionally, pointers from any holder into the atoms zone are allowed
1277 // since all holders are traced when we collect the atoms zone.
1279 // If you added a holder that has pointers into multiple zones do not
1280 // use NS_IMPL_CYCLE_COLLECTION_SINGLE_ZONE_SCRIPT_HOLDER_CLASS.
1281 MOZ_CRASH_UNSAFE_PRINTF(
1282 "JS holder %s contains pointers to GC things in more than one zone ("
1283 "found in %s)\n",
1284 mClassName, aName);
1287 virtual void Trace(JS::Heap<JS::Value>* aPtr, const char* aName,
1288 void* aClosure) const override {
1289 JS::Value value = aPtr->unbarrieredGet();
1290 if (value.isGCThing()) {
1291 checkZone(JS::GetGCThingZone(value.toGCCellPtr()), aName);
1294 virtual void Trace(JS::Heap<jsid>* aPtr, const char* aName,
1295 void* aClosure) const override {
1296 jsid id = aPtr->unbarrieredGet();
1297 if (id.isGCThing()) {
1298 MOZ_ASSERT(JS::IsAtomsZone(JS::GetTenuredGCThingZone(id.toGCCellPtr())));
1301 virtual void Trace(JS::Heap<JSObject*>* aPtr, const char* aName,
1302 void* aClosure) const override {
1303 JSObject* obj = aPtr->unbarrieredGet();
1304 if (obj) {
1305 checkZone(js::GetObjectZoneFromAnyThread(obj), aName);
1308 virtual void Trace(nsWrapperCache* aPtr, const char* aName,
1309 void* aClosure) const override {
1310 JSObject* obj = aPtr->GetWrapperPreserveColor();
1311 if (obj) {
1312 checkZone(js::GetObjectZoneFromAnyThread(obj), aName);
1315 virtual void Trace(JS::TenuredHeap<JSObject*>* aPtr, const char* aName,
1316 void* aClosure) const override {
1317 JSObject* obj = aPtr->unbarrieredGetPtr();
1318 if (obj) {
1319 checkZone(js::GetObjectZoneFromAnyThread(obj), aName);
1322 virtual void Trace(JS::Heap<JSString*>* aPtr, const char* aName,
1323 void* aClosure) const override {
1324 JSString* str = aPtr->unbarrieredGet();
1325 if (str) {
1326 checkZone(JS::GetStringZone(str), aName);
1329 virtual void Trace(JS::Heap<JSScript*>* aPtr, const char* aName,
1330 void* aClosure) const override {
1331 JSScript* script = aPtr->unbarrieredGet();
1332 if (script) {
1333 checkZone(JS::GetTenuredGCThingZone(JS::GCCellPtr(script)), aName);
1336 virtual void Trace(JS::Heap<JSFunction*>* aPtr, const char* aName,
1337 void* aClosure) const override {
1338 JSFunction* fun = aPtr->unbarrieredGet();
1339 if (fun) {
1340 checkZone(js::GetObjectZoneFromAnyThread(JS_GetFunctionObject(fun)),
1341 aName);
1346 static inline void CheckHolderIsSingleZone(
1347 void* aHolder, nsCycleCollectionParticipant* aParticipant,
1348 JS::Zone* aZone) {
1349 CheckZoneTracer tracer(aParticipant->ClassName(), aZone);
1350 aParticipant->Trace(aHolder, tracer, nullptr);
1353 #endif
1355 static inline bool ShouldCheckSingleZoneHolders() {
1356 #if defined(DEBUG)
1357 return true;
1358 #elif defined(NIGHTLY_BUILD) || defined(MOZ_DEV_EDITION)
1359 // Don't check every time to avoid performance impact.
1360 return rand() % 256 == 0;
1361 #else
1362 return false;
1363 #endif
1366 #ifdef NS_BUILD_REFCNT_LOGGING
1367 void CycleCollectedJSRuntime::TraceAllNativeGrayRoots(JSTracer* aTracer) {
1368 MOZ_RELEASE_ASSERT(mHolderIter.isNothing());
1369 JS::SliceBudget budget = JS::SliceBudget::unlimited();
1370 MOZ_ALWAYS_TRUE(
1371 TraceNativeGrayRoots(aTracer, JSHolderMap::AllHolders, budget));
1373 #endif
1375 bool CycleCollectedJSRuntime::TraceNativeGrayRoots(
1376 JSTracer* aTracer, JSHolderMap::WhichHolders aWhich,
1377 JS::SliceBudget& aBudget) {
1378 if (!mHolderIter) {
1379 // NB: This is here just to preserve the existing XPConnect order. I doubt
1380 // it would hurt to do this after the JS holders.
1381 TraceAdditionalNativeGrayRoots(aTracer);
1383 mHolderIter.emplace(mJSHolders, aWhich);
1384 aBudget.forceCheck();
1385 } else {
1386 // Holders may have been removed between slices, so we may need to update
1387 // the iterator.
1388 mHolderIter->UpdateForRemovals();
1391 bool finished = TraceJSHolders(aTracer, *mHolderIter, aBudget);
1392 if (finished) {
1393 mHolderIter.reset();
1396 return finished;
1399 class GetHolderAddressFunctor : public JS::TracingContext::Functor {
1400 public:
1401 GetHolderAddressFunctor() = default;
1403 virtual void operator()(JS::TracingContext* aTrc, const char* aName,
1404 char* aBuf, size_t aBufSize) override {
1405 SprintfBuf(aBuf, aBufSize, "%s, holder 0x%p", aName, mHolder);
1408 void SetHolder(void* aHolder) { mHolder = aHolder; }
1410 private:
1411 void* mHolder = nullptr;
1414 bool CycleCollectedJSRuntime::TraceJSHolders(JSTracer* aTracer,
1415 JSHolderMap::Iter& aIter,
1416 JS::SliceBudget& aBudget) {
1417 bool checkSingleZoneHolders = ShouldCheckSingleZoneHolders();
1418 GetHolderAddressFunctor functor;
1419 JS::AutoTracingDetails tracingDetails(aTracer, functor);
1421 while (!aIter.Done() && !aBudget.isOverBudget()) {
1422 void* holder = aIter->mHolder;
1423 nsScriptObjectTracer* tracer = aIter->mTracer;
1425 #ifdef CHECK_SINGLE_ZONE_JS_HOLDERS
1426 if (checkSingleZoneHolders && tracer->IsSingleZoneJSHolder()) {
1427 CheckHolderIsSingleZone(holder, tracer, aIter.Zone());
1429 #else
1430 Unused << checkSingleZoneHolders;
1431 #endif
1433 functor.SetHolder(holder);
1434 tracer->Trace(holder, JsGcTracer(), aTracer);
1435 functor.SetHolder(nullptr);
1437 aIter.Next();
1438 aBudget.step();
1441 return aIter.Done();
1444 void CycleCollectedJSRuntime::AddJSHolder(void* aHolder,
1445 nsScriptObjectTracer* aTracer,
1446 JS::Zone* aZone) {
1447 mJSHolders.Put(aHolder, aTracer, aZone);
1450 struct ClearJSHolder : public TraceCallbacks {
1451 virtual void Trace(JS::Heap<JS::Value>* aPtr, const char*,
1452 void*) const override {
1453 aPtr->setUndefined();
1456 virtual void Trace(JS::Heap<jsid>* aPtr, const char*, void*) const override {
1457 *aPtr = JS::PropertyKey::Void();
1460 virtual void Trace(JS::Heap<JSObject*>* aPtr, const char*,
1461 void*) const override {
1462 *aPtr = nullptr;
1465 virtual void Trace(nsWrapperCache* aPtr, const char* aName,
1466 void* aClosure) const override {
1467 aPtr->ClearWrapper();
1470 virtual void Trace(JS::TenuredHeap<JSObject*>* aPtr, const char*,
1471 void*) const override {
1472 *aPtr = nullptr;
1475 virtual void Trace(JS::Heap<JSString*>* aPtr, const char*,
1476 void*) const override {
1477 *aPtr = nullptr;
1480 virtual void Trace(JS::Heap<JSScript*>* aPtr, const char*,
1481 void*) const override {
1482 *aPtr = nullptr;
1485 virtual void Trace(JS::Heap<JSFunction*>* aPtr, const char*,
1486 void*) const override {
1487 *aPtr = nullptr;
1491 void CycleCollectedJSRuntime::RemoveJSHolder(void* aHolder) {
1492 nsScriptObjectTracer* tracer = mJSHolders.Extract(aHolder);
1493 if (tracer) {
1494 // Bug 1531951: The analysis can't see through the virtual call but we know
1495 // that the ClearJSHolder tracer will never GC.
1496 JS::AutoSuppressGCAnalysis nogc;
1497 tracer->Trace(aHolder, ClearJSHolder(), nullptr);
1501 #ifdef DEBUG
1502 static void AssertNoGcThing(JS::GCCellPtr aGCThing, const char* aName,
1503 void* aClosure) {
1504 MOZ_ASSERT(!aGCThing);
1507 void CycleCollectedJSRuntime::AssertNoObjectsToTrace(void* aPossibleJSHolder) {
1508 nsScriptObjectTracer* tracer = mJSHolders.Get(aPossibleJSHolder);
1509 if (tracer) {
1510 tracer->Trace(aPossibleJSHolder, TraceCallbackFunc(AssertNoGcThing),
1511 nullptr);
1514 #endif
1516 nsCycleCollectionParticipant* CycleCollectedJSRuntime::GCThingParticipant() {
1517 return &mGCThingCycleCollectorGlobal;
1520 nsCycleCollectionParticipant* CycleCollectedJSRuntime::ZoneParticipant() {
1521 return &mJSZoneCycleCollectorGlobal;
1524 nsresult CycleCollectedJSRuntime::TraverseRoots(
1525 nsCycleCollectionNoteRootCallback& aCb) {
1526 TraverseNativeRoots(aCb);
1528 NoteWeakMapsTracer trc(mJSRuntime, aCb);
1529 js::TraceWeakMaps(&trc);
1531 return NS_OK;
1534 bool CycleCollectedJSRuntime::UsefulToMergeZones() const { return false; }
1536 void CycleCollectedJSRuntime::FixWeakMappingGrayBits() const {
1537 MOZ_ASSERT(!JS::IsIncrementalGCInProgress(mJSRuntime),
1538 "Don't call FixWeakMappingGrayBits during a GC.");
1539 FixWeakMappingGrayBitsTracer fixer(mJSRuntime);
1540 fixer.FixAll();
1543 void CycleCollectedJSRuntime::CheckGrayBits() const {
1544 MOZ_ASSERT(!JS::IsIncrementalGCInProgress(mJSRuntime),
1545 "Don't call CheckGrayBits during a GC.");
1547 #ifndef ANDROID
1548 // Bug 1346874 - The gray state check is expensive. Android tests are already
1549 // slow enough that this check can easily push them over the threshold to a
1550 // timeout.
1552 MOZ_ASSERT(js::CheckGrayMarkingState(mJSRuntime));
1553 MOZ_ASSERT(CheckWeakMappingGrayBitsTracer::Check(mJSRuntime));
1554 #endif
1557 bool CycleCollectedJSRuntime::AreGCGrayBitsValid() const {
1558 return js::AreGCGrayBitsValid(mJSRuntime);
1561 void CycleCollectedJSRuntime::GarbageCollect(JS::GCOptions aOptions,
1562 JS::GCReason aReason) const {
1563 JSContext* cx = CycleCollectedJSContext::Get()->Context();
1564 JS::PrepareForFullGC(cx);
1565 JS::NonIncrementalGC(cx, aOptions, aReason);
1568 void CycleCollectedJSRuntime::JSObjectsTenured(JS::GCContext* aGCContext) {
1569 NurseryObjectsVector objects;
1570 std::swap(objects, mNurseryObjects);
1572 for (auto iter = objects.Iter(); !iter.Done(); iter.Next()) {
1573 nsWrapperCache* cache = iter.Get();
1574 JSObject* wrapper = cache->GetWrapperMaybeDead();
1575 MOZ_DIAGNOSTIC_ASSERT(wrapper);
1577 if (js::gc::InCollectedNurseryRegion(wrapper)) {
1578 MOZ_ASSERT(!cache->PreservingWrapper());
1579 const JSClass* jsClass = JS::GetClass(wrapper);
1580 jsClass->doFinalize(aGCContext, wrapper);
1581 continue;
1584 if (js::gc::IsInsideNursery(wrapper)) {
1585 mNurseryObjects.InfallibleAppend(cache);
1590 void CycleCollectedJSRuntime::NurseryWrapperAdded(nsWrapperCache* aCache) {
1591 MOZ_ASSERT(aCache);
1592 MOZ_ASSERT(aCache->GetWrapperMaybeDead());
1593 MOZ_ASSERT(!JS::ObjectIsTenured(aCache->GetWrapperMaybeDead()));
1594 mNurseryObjects.InfallibleAppend(aCache);
1597 void CycleCollectedJSRuntime::DeferredFinalize(
1598 DeferredFinalizeAppendFunction aAppendFunc, DeferredFinalizeFunction aFunc,
1599 void* aThing) {
1600 // Tell the analysis that the function pointers will not GC.
1601 JS::AutoSuppressGCAnalysis suppress;
1602 mDeferredFinalizerTable.WithEntryHandle(aFunc, [&](auto&& entry) {
1603 if (entry) {
1604 aAppendFunc(entry.Data(), aThing);
1605 } else {
1606 entry.Insert(aAppendFunc(nullptr, aThing));
1611 void CycleCollectedJSRuntime::DeferredFinalize(nsISupports* aSupports) {
1612 typedef DeferredFinalizerImpl<nsISupports> Impl;
1613 DeferredFinalize(Impl::AppendDeferredFinalizePointer, Impl::DeferredFinalize,
1614 aSupports);
1617 void CycleCollectedJSRuntime::DumpJSHeap(FILE* aFile) {
1618 JSContext* cx = CycleCollectedJSContext::Get()->Context();
1620 mozilla::MallocSizeOf mallocSizeOf =
1621 PR_GetEnv("MOZ_GC_LOG_SIZE") ? moz_malloc_size_of : nullptr;
1622 js::DumpHeap(cx, aFile, js::CollectNurseryBeforeDump, mallocSizeOf);
1625 IncrementalFinalizeRunnable::IncrementalFinalizeRunnable(
1626 CycleCollectedJSRuntime* aRt, DeferredFinalizerTable& aFinalizers)
1627 : DiscardableRunnable("IncrementalFinalizeRunnable"),
1628 mRuntime(aRt),
1629 mFinalizeFunctionToRun(0),
1630 mReleasing(false) {
1631 for (auto iter = aFinalizers.Iter(); !iter.Done(); iter.Next()) {
1632 DeferredFinalizeFunction& function = iter.Key();
1633 void*& data = iter.Data();
1635 DeferredFinalizeFunctionHolder* holder =
1636 mDeferredFinalizeFunctions.AppendElement();
1637 holder->run = function;
1638 holder->data = data;
1640 iter.Remove();
1642 MOZ_ASSERT(mDeferredFinalizeFunctions.Length());
1645 IncrementalFinalizeRunnable::~IncrementalFinalizeRunnable() {
1646 MOZ_ASSERT(!mDeferredFinalizeFunctions.Length());
1647 MOZ_ASSERT(!mRuntime);
1650 void IncrementalFinalizeRunnable::ReleaseNow(bool aLimited) {
1651 if (mReleasing) {
1652 NS_WARNING("Re-entering ReleaseNow");
1653 return;
1656 AUTO_PROFILER_LABEL("IncrementalFinalizeRunnable::ReleaseNow",
1657 GCCC_Finalize);
1659 mozilla::AutoRestore<bool> ar(mReleasing);
1660 mReleasing = true;
1661 MOZ_ASSERT(mDeferredFinalizeFunctions.Length() != 0,
1662 "We should have at least ReleaseSliceNow to run");
1663 MOZ_ASSERT(mFinalizeFunctionToRun < mDeferredFinalizeFunctions.Length(),
1664 "No more finalizers to run?");
1666 TimeDuration sliceTime = TimeDuration::FromMilliseconds(SliceMillis);
1667 TimeStamp started = aLimited ? TimeStamp::Now() : TimeStamp();
1668 bool timeout = false;
1669 do {
1670 const DeferredFinalizeFunctionHolder& function =
1671 mDeferredFinalizeFunctions[mFinalizeFunctionToRun];
1672 if (aLimited) {
1673 bool done = false;
1674 while (!timeout && !done) {
1676 * We don't want to read the clock too often, so we try to
1677 * release slices of 100 items.
1679 done = function.run(100, function.data);
1680 timeout = TimeStamp::Now() - started >= sliceTime;
1682 if (done) {
1683 ++mFinalizeFunctionToRun;
1685 if (timeout) {
1686 break;
1688 } else {
1689 while (!function.run(UINT32_MAX, function.data));
1690 ++mFinalizeFunctionToRun;
1692 } while (mFinalizeFunctionToRun < mDeferredFinalizeFunctions.Length());
1695 if (mFinalizeFunctionToRun == mDeferredFinalizeFunctions.Length()) {
1696 MOZ_ASSERT(mRuntime->mFinalizeRunnable == this);
1697 mDeferredFinalizeFunctions.Clear();
1698 CycleCollectedJSRuntime* runtime = mRuntime;
1699 mRuntime = nullptr;
1700 // NB: This may delete this!
1701 runtime->mFinalizeRunnable = nullptr;
1705 NS_IMETHODIMP
1706 IncrementalFinalizeRunnable::Run() {
1707 if (!mDeferredFinalizeFunctions.Length()) {
1708 /* These items were already processed synchronously in JSGC_END. */
1709 MOZ_ASSERT(!mRuntime);
1710 return NS_OK;
1713 MOZ_ASSERT(mRuntime->mFinalizeRunnable == this);
1714 TimeStamp start = TimeStamp::Now();
1715 ReleaseNow(true);
1717 if (mDeferredFinalizeFunctions.Length()) {
1718 nsresult rv = NS_DispatchToCurrentThread(this);
1719 if (NS_FAILED(rv)) {
1720 ReleaseNow(false);
1722 } else {
1723 MOZ_ASSERT(!mRuntime);
1726 uint32_t duration = (uint32_t)((TimeStamp::Now() - start).ToMilliseconds());
1727 Telemetry::Accumulate(Telemetry::DEFERRED_FINALIZE_ASYNC, duration);
1729 return NS_OK;
1732 void CycleCollectedJSRuntime::FinalizeDeferredThings(
1733 DeferredFinalizeType aType) {
1734 // If mFinalizeRunnable isn't null, we didn't finalize everything from the
1735 // previous GC.
1736 if (mFinalizeRunnable) {
1737 if (aType == FinalizeLater) {
1738 // We need to defer all finalization until we return to the event loop,
1739 // so leave things alone. Any new objects to be finalized from the current
1740 // GC will be handled by the existing mFinalizeRunnable.
1741 return;
1743 MOZ_ASSERT(aType == FinalizeIncrementally || aType == FinalizeNow);
1744 // If we're finalizing incrementally, we don't want finalizers to build up,
1745 // so try to finish them off now.
1746 // If we're finalizing synchronously, also go ahead and clear them out,
1747 // so we make sure as much as possible is freed.
1748 mFinalizeRunnable->ReleaseNow(false);
1749 if (mFinalizeRunnable) {
1750 // If we re-entered ReleaseNow, we couldn't delete mFinalizeRunnable and
1751 // we need to just continue processing it.
1752 return;
1756 // If there's nothing to finalize, don't create a new runnable.
1757 if (mDeferredFinalizerTable.Count() == 0) {
1758 return;
1761 mFinalizeRunnable =
1762 new IncrementalFinalizeRunnable(this, mDeferredFinalizerTable);
1764 // Everything should be gone now.
1765 MOZ_ASSERT(mDeferredFinalizerTable.Count() == 0);
1767 if (aType == FinalizeNow) {
1768 mFinalizeRunnable->ReleaseNow(false);
1769 MOZ_ASSERT(!mFinalizeRunnable);
1770 } else {
1771 MOZ_ASSERT(aType == FinalizeIncrementally || aType == FinalizeLater);
1772 NS_DispatchToCurrentThreadQueue(do_AddRef(mFinalizeRunnable), 2500,
1773 EventQueuePriority::Idle);
1777 const char* CycleCollectedJSRuntime::OOMStateToString(
1778 const OOMState aOomState) const {
1779 switch (aOomState) {
1780 case OOMState::OK:
1781 return "OK";
1782 case OOMState::Reporting:
1783 return "Reporting";
1784 case OOMState::Reported:
1785 return "Reported";
1786 case OOMState::Recovered:
1787 return "Recovered";
1788 default:
1789 MOZ_ASSERT_UNREACHABLE("OOMState holds an invalid value");
1790 return "Unknown";
1794 bool CycleCollectedJSRuntime::OOMReported() {
1795 return mOutOfMemoryState == OOMState::Reported;
1798 void CycleCollectedJSRuntime::AnnotateAndSetOutOfMemory(OOMState* aStatePtr,
1799 OOMState aNewState) {
1800 *aStatePtr = aNewState;
1801 CrashReporter::Annotation annotation =
1802 (aStatePtr == &mOutOfMemoryState)
1803 ? CrashReporter::Annotation::JSOutOfMemory
1804 : CrashReporter::Annotation::JSLargeAllocationFailure;
1806 CrashReporter::RecordAnnotationCString(annotation,
1807 OOMStateToString(aNewState));
1810 void CycleCollectedJSRuntime::OnGC(JSContext* aContext, JSGCStatus aStatus,
1811 JS::GCReason aReason) {
1812 switch (aStatus) {
1813 case JSGC_BEGIN:
1814 MOZ_RELEASE_ASSERT(mHolderIter.isNothing());
1815 nsCycleCollector_prepareForGarbageCollection();
1816 PrepareWaitingZonesForGC();
1817 break;
1818 case JSGC_END: {
1819 MOZ_RELEASE_ASSERT(mHolderIter.isNothing());
1820 if (mOutOfMemoryState == OOMState::Reported) {
1821 AnnotateAndSetOutOfMemory(&mOutOfMemoryState, OOMState::Recovered);
1823 if (mLargeAllocationFailureState == OOMState::Reported) {
1824 AnnotateAndSetOutOfMemory(&mLargeAllocationFailureState,
1825 OOMState::Recovered);
1828 DeferredFinalizeType finalizeType;
1829 if (JS_IsExceptionPending(aContext)) {
1830 // There is a pending exception. The finalizers are not set up to run
1831 // in that state, so don't run the finalizer until we've returned to the
1832 // event loop.
1833 finalizeType = FinalizeLater;
1834 } else if (JS::InternalGCReason(aReason)) {
1835 if (aReason == JS::GCReason::DESTROY_RUNTIME) {
1836 // We're shutting down, so we need to destroy things immediately.
1837 finalizeType = FinalizeNow;
1838 } else {
1839 // We may be in the middle of running some code that the JIT has
1840 // assumed can't have certain kinds of side effects. Finalizers can do
1841 // all sorts of things, such as run JS, so we want to run them later,
1842 // after we've returned to the event loop.
1843 finalizeType = FinalizeLater;
1845 } else if (JS::WasIncrementalGC(mJSRuntime)) {
1846 // The GC was incremental, so we probably care about pauses. Try to
1847 // break up finalization, but it is okay if we do some now.
1848 finalizeType = FinalizeIncrementally;
1849 } else {
1850 // If we're running a synchronous GC, we probably want to free things as
1851 // quickly as possible. This can happen during testing or if memory is
1852 // low.
1853 finalizeType = FinalizeNow;
1855 FinalizeDeferredThings(finalizeType);
1857 break;
1859 default:
1860 MOZ_CRASH();
1863 CustomGCCallback(aStatus);
1866 void CycleCollectedJSRuntime::OnOutOfMemory() {
1867 AnnotateAndSetOutOfMemory(&mOutOfMemoryState, OOMState::Reporting);
1868 CustomOutOfMemoryCallback();
1869 AnnotateAndSetOutOfMemory(&mOutOfMemoryState, OOMState::Reported);
1872 void CycleCollectedJSRuntime::SetLargeAllocationFailure(OOMState aNewState) {
1873 AnnotateAndSetOutOfMemory(&mLargeAllocationFailureState, aNewState);
1876 void CycleCollectedJSRuntime::PrepareWaitingZonesForGC() {
1877 JSContext* cx = CycleCollectedJSContext::Get()->Context();
1878 if (mZonesWaitingForGC.Count() == 0) {
1879 JS::PrepareForFullGC(cx);
1880 } else {
1881 for (const auto& key : mZonesWaitingForGC) {
1882 JS::PrepareZoneForGC(cx, key);
1884 mZonesWaitingForGC.Clear();
1888 /* static */
1889 void CycleCollectedJSRuntime::OnZoneDestroyed(JS::GCContext* aGcx,
1890 JS::Zone* aZone) {
1891 // Remove the zone from the set of zones waiting for GC, if present. This can
1892 // happen if a zone is added to the set during an incremental GC in which it
1893 // is later destroyed.
1894 CycleCollectedJSRuntime* runtime = Get();
1895 runtime->mZonesWaitingForGC.Remove(aZone);
1898 void CycleCollectedJSRuntime::EnvironmentPreparer::invoke(
1899 JS::HandleObject global, js::ScriptEnvironmentPreparer::Closure& closure) {
1900 MOZ_ASSERT(JS_IsGlobalObject(global));
1901 nsIGlobalObject* nativeGlobal = xpc::NativeGlobal(global);
1903 // Not much we can do if we simply don't have a usable global here...
1904 NS_ENSURE_TRUE_VOID(nativeGlobal && nativeGlobal->HasJSGlobal());
1906 AutoEntryScript aes(nativeGlobal, "JS-engine-initiated execution");
1908 MOZ_ASSERT(!JS_IsExceptionPending(aes.cx()));
1910 DebugOnly<bool> ok = closure(aes.cx());
1912 MOZ_ASSERT_IF(ok, !JS_IsExceptionPending(aes.cx()));
1914 // The AutoEntryScript will check for JS_IsExceptionPending on the
1915 // JSContext and report it as needed as it comes off the stack.
1918 /* static */
1919 CycleCollectedJSRuntime* CycleCollectedJSRuntime::Get() {
1920 auto context = CycleCollectedJSContext::Get();
1921 if (context) {
1922 return context->Runtime();
1924 return nullptr;
1927 #ifdef MOZ_JS_DEV_ERROR_INTERCEPTOR
1929 namespace js {
1930 extern void DumpValue(const JS::Value& val);
1933 void CycleCollectedJSRuntime::ErrorInterceptor::Shutdown(JSRuntime* rt) {
1934 JS_SetErrorInterceptorCallback(rt, nullptr);
1935 mThrownError.reset();
1938 /* virtual */
1939 void CycleCollectedJSRuntime::ErrorInterceptor::interceptError(
1940 JSContext* cx, JS::HandleValue exn) {
1941 if (mThrownError) {
1942 // We already have an error, we don't need anything more.
1943 return;
1946 if (!nsContentUtils::ThreadsafeIsSystemCaller(cx)) {
1947 // We are only interested in chrome code.
1948 return;
1951 const auto type = JS_GetErrorType(exn);
1952 if (!type) {
1953 // This is not one of the primitive error types.
1954 return;
1957 switch (*type) {
1958 case JSExnType::JSEXN_REFERENCEERR:
1959 case JSExnType::JSEXN_SYNTAXERR:
1960 break;
1961 default:
1962 // Not one of the errors we are interested in.
1963 // Note that we are not interested in instances of `TypeError`
1964 // for the time being, as DOM (ab)uses this constructor to represent
1965 // all sorts of errors that are not even remotely related to type
1966 // errors (e.g. some network errors).
1967 // If we ever have a mechanism to differentiate between DOM-thrown
1968 // and SpiderMonkey-thrown instances of `TypeError`, we should
1969 // consider watching for `TypeError` here.
1970 return;
1973 // Now copy the details of the exception locally.
1974 // While copying the details of an exception could be expensive, in most runs,
1975 // this will be done at most once during the execution of the process, so the
1976 // total cost should be reasonable.
1978 ErrorDetails details;
1979 details.mType = *type;
1980 // If `exn` isn't an exception object, `ExtractErrorValues` could end up
1981 // calling `toString()`, which could in turn end up throwing an error. While
1982 // this should work, we want to avoid that complex use case. Fortunately, we
1983 // have already checked above that `exn` is an exception object, so nothing
1984 // such should happen.
1985 nsContentUtils::ExtractErrorValues(cx, exn, details.mFilename, &details.mLine,
1986 &details.mColumn, details.mMessage);
1988 JS::UniqueChars buf =
1989 JS::FormatStackDump(cx, /* showArgs = */ false, /* showLocals = */ false,
1990 /* showThisProps = */ false);
1991 CopyUTF8toUTF16(mozilla::MakeStringSpan(buf.get()), details.mStack);
1993 mThrownError.emplace(std::move(details));
1996 void CycleCollectedJSRuntime::ClearRecentDevError() {
1997 mErrorInterceptor.mThrownError.reset();
2000 bool CycleCollectedJSRuntime::GetRecentDevError(
2001 JSContext* cx, JS::MutableHandle<JS::Value> error) {
2002 if (!mErrorInterceptor.mThrownError) {
2003 return true;
2006 // Create a copy of the exception.
2007 JS::RootedObject obj(cx, JS_NewPlainObject(cx));
2008 if (!obj) {
2009 return false;
2012 JS::RootedValue message(cx);
2013 JS::RootedValue filename(cx);
2014 JS::RootedValue stack(cx);
2015 if (!ToJSValue(cx, mErrorInterceptor.mThrownError->mMessage, &message) ||
2016 !ToJSValue(cx, mErrorInterceptor.mThrownError->mFilename, &filename) ||
2017 !ToJSValue(cx, mErrorInterceptor.mThrownError->mStack, &stack)) {
2018 return false;
2021 // Build the object.
2022 const auto FLAGS = JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT;
2023 if (!JS_DefineProperty(cx, obj, "message", message, FLAGS) ||
2024 !JS_DefineProperty(cx, obj, "fileName", filename, FLAGS) ||
2025 !JS_DefineProperty(cx, obj, "lineNumber",
2026 mErrorInterceptor.mThrownError->mLine, FLAGS) ||
2027 !JS_DefineProperty(cx, obj, "stack", stack, FLAGS)) {
2028 return false;
2031 // Pass the result.
2032 error.setObject(*obj);
2033 return true;
2035 #endif // MOZ_JS_DEV_ERROR_INTERCEPTOR
2037 #undef MOZ_JS_DEV_ERROR_INTERCEPTOR