Backed out changeset bd51857879db (bug 1862957) for causing WakeLock related failures...
[gecko.git] / xpcom / base / CycleCollectedJSRuntime.cpp
blob3813b12f17b7e7ea21d157265f24b24d3d41a021
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 "nsStringBuffer.h"
101 #include "nsWrapperCache.h"
102 #include "prenv.h"
104 #if defined(XP_MACOSX)
105 # include "nsMacUtilsImpl.h"
106 #endif
108 #include "nsThread.h"
109 #include "nsThreadUtils.h"
110 #include "xpcpublic.h"
112 #ifdef NIGHTLY_BUILD
113 // For performance reasons, we make the JS Dev Error Interceptor a Nightly-only
114 // feature.
115 # define MOZ_JS_DEV_ERROR_INTERCEPTOR = 1
116 #endif // NIGHTLY_BUILD
118 using namespace mozilla;
119 using namespace mozilla::dom;
121 namespace mozilla {
123 struct DeferredFinalizeFunctionHolder {
124 DeferredFinalizeFunction run;
125 void* data;
128 class IncrementalFinalizeRunnable : public DiscardableRunnable {
129 typedef AutoTArray<DeferredFinalizeFunctionHolder, 16> DeferredFinalizeArray;
130 typedef CycleCollectedJSRuntime::DeferredFinalizerTable
131 DeferredFinalizerTable;
133 CycleCollectedJSRuntime* mRuntime;
134 DeferredFinalizeArray mDeferredFinalizeFunctions;
135 uint32_t mFinalizeFunctionToRun;
136 bool mReleasing;
138 static const PRTime SliceMillis = 5; /* ms */
140 public:
141 IncrementalFinalizeRunnable(CycleCollectedJSRuntime* aRt,
142 DeferredFinalizerTable& aFinalizerTable);
143 virtual ~IncrementalFinalizeRunnable();
145 void ReleaseNow(bool aLimited);
147 NS_DECL_NSIRUNNABLE
150 } // namespace mozilla
152 struct NoteWeakMapChildrenTracer : public JS::CallbackTracer {
153 NoteWeakMapChildrenTracer(JSRuntime* aRt,
154 nsCycleCollectionNoteRootCallback& aCb)
155 : JS::CallbackTracer(aRt, JS::TracerKind::Callback),
156 mCb(aCb),
157 mTracedAny(false),
158 mMap(nullptr),
159 mKey(nullptr),
160 mKeyDelegate(nullptr) {}
161 void onChild(JS::GCCellPtr aThing, const char* name) override;
162 nsCycleCollectionNoteRootCallback& mCb;
163 bool mTracedAny;
164 JSObject* mMap;
165 JS::GCCellPtr mKey;
166 JSObject* mKeyDelegate;
169 void NoteWeakMapChildrenTracer::onChild(JS::GCCellPtr aThing,
170 const char* name) {
171 if (aThing.is<JSString>()) {
172 return;
175 if (!JS::GCThingIsMarkedGrayInCC(aThing) && !mCb.WantAllTraces()) {
176 return;
179 if (JS::IsCCTraceKind(aThing.kind())) {
180 mCb.NoteWeakMapping(mMap, mKey, mKeyDelegate, aThing);
181 mTracedAny = true;
182 } else {
183 JS::TraceChildren(this, aThing);
187 struct NoteWeakMapsTracer : public js::WeakMapTracer {
188 NoteWeakMapsTracer(JSRuntime* aRt, nsCycleCollectionNoteRootCallback& aCccb)
189 : js::WeakMapTracer(aRt), mCb(aCccb), mChildTracer(aRt, aCccb) {}
190 void trace(JSObject* aMap, JS::GCCellPtr aKey, JS::GCCellPtr aValue) override;
191 nsCycleCollectionNoteRootCallback& mCb;
192 NoteWeakMapChildrenTracer mChildTracer;
195 void NoteWeakMapsTracer::trace(JSObject* aMap, JS::GCCellPtr aKey,
196 JS::GCCellPtr aValue) {
197 // If nothing that could be held alive by this entry is marked gray, return.
198 if ((!aKey || !JS::GCThingIsMarkedGrayInCC(aKey)) &&
199 MOZ_LIKELY(!mCb.WantAllTraces())) {
200 if (!aValue || !JS::GCThingIsMarkedGrayInCC(aValue) ||
201 aValue.is<JSString>()) {
202 return;
206 // The cycle collector can only properly reason about weak maps if it can
207 // reason about the liveness of their keys, which in turn requires that
208 // the key can be represented in the cycle collector graph. All existing
209 // uses of weak maps use either objects or scripts as keys, which are okay.
210 MOZ_ASSERT(JS::IsCCTraceKind(aKey.kind()));
212 // As an emergency fallback for non-debug builds, if the key is not
213 // representable in the cycle collector graph, we treat it as marked. This
214 // can cause leaks, but is preferable to ignoring the binding, which could
215 // cause the cycle collector to free live objects.
216 if (!JS::IsCCTraceKind(aKey.kind())) {
217 aKey = nullptr;
220 JSObject* kdelegate = nullptr;
221 if (aKey.is<JSObject>()) {
222 kdelegate = js::UncheckedUnwrapWithoutExpose(&aKey.as<JSObject>());
225 if (JS::IsCCTraceKind(aValue.kind())) {
226 mCb.NoteWeakMapping(aMap, aKey, kdelegate, aValue);
227 } else {
228 mChildTracer.mTracedAny = false;
229 mChildTracer.mMap = aMap;
230 mChildTracer.mKey = aKey;
231 mChildTracer.mKeyDelegate = kdelegate;
233 if (!aValue.is<JSString>()) {
234 JS::TraceChildren(&mChildTracer, aValue);
237 // The delegate could hold alive the key, so report something to the CC
238 // if we haven't already.
239 if (!mChildTracer.mTracedAny && aKey && JS::GCThingIsMarkedGrayInCC(aKey) &&
240 kdelegate) {
241 mCb.NoteWeakMapping(aMap, aKey, kdelegate, nullptr);
246 // Report whether the key or value of a weak mapping entry are gray but need to
247 // be marked black.
248 static void ShouldWeakMappingEntryBeBlack(JSObject* aMap, JS::GCCellPtr aKey,
249 JS::GCCellPtr aValue,
250 bool* aKeyShouldBeBlack,
251 bool* aValueShouldBeBlack) {
252 *aKeyShouldBeBlack = false;
253 *aValueShouldBeBlack = false;
255 // If nothing that could be held alive by this entry is marked gray, return.
256 bool keyMightNeedMarking = aKey && JS::GCThingIsMarkedGrayInCC(aKey);
257 bool valueMightNeedMarking = aValue && JS::GCThingIsMarkedGrayInCC(aValue) &&
258 aValue.kind() != JS::TraceKind::String;
259 if (!keyMightNeedMarking && !valueMightNeedMarking) {
260 return;
263 if (!JS::IsCCTraceKind(aKey.kind())) {
264 aKey = nullptr;
267 if (keyMightNeedMarking && aKey.is<JSObject>()) {
268 JSObject* kdelegate =
269 js::UncheckedUnwrapWithoutExpose(&aKey.as<JSObject>());
270 if (kdelegate && !JS::ObjectIsMarkedGray(kdelegate) &&
271 (!aMap || !JS::ObjectIsMarkedGray(aMap))) {
272 *aKeyShouldBeBlack = true;
276 if (aValue && JS::GCThingIsMarkedGrayInCC(aValue) &&
277 (!aKey || !JS::GCThingIsMarkedGrayInCC(aKey)) &&
278 (!aMap || !JS::ObjectIsMarkedGray(aMap)) &&
279 aValue.kind() != JS::TraceKind::Shape) {
280 *aValueShouldBeBlack = true;
284 struct FixWeakMappingGrayBitsTracer : public js::WeakMapTracer {
285 explicit FixWeakMappingGrayBitsTracer(JSRuntime* aRt)
286 : js::WeakMapTracer(aRt) {}
288 void FixAll() {
289 do {
290 mAnyMarked = false;
291 js::TraceWeakMaps(this);
292 } while (mAnyMarked);
295 void trace(JSObject* aMap, JS::GCCellPtr aKey,
296 JS::GCCellPtr aValue) override {
297 bool keyShouldBeBlack;
298 bool valueShouldBeBlack;
299 ShouldWeakMappingEntryBeBlack(aMap, aKey, aValue, &keyShouldBeBlack,
300 &valueShouldBeBlack);
301 if (keyShouldBeBlack && JS::UnmarkGrayGCThingRecursively(aKey)) {
302 mAnyMarked = true;
305 if (valueShouldBeBlack && JS::UnmarkGrayGCThingRecursively(aValue)) {
306 mAnyMarked = true;
310 MOZ_INIT_OUTSIDE_CTOR bool mAnyMarked;
313 #ifdef DEBUG
314 // Check whether weak maps are marked correctly according to the logic above.
315 struct CheckWeakMappingGrayBitsTracer : public js::WeakMapTracer {
316 explicit CheckWeakMappingGrayBitsTracer(JSRuntime* aRt)
317 : js::WeakMapTracer(aRt), mFailed(false) {}
319 static bool Check(JSRuntime* aRt) {
320 CheckWeakMappingGrayBitsTracer tracer(aRt);
321 js::TraceWeakMaps(&tracer);
322 return !tracer.mFailed;
325 void trace(JSObject* aMap, JS::GCCellPtr aKey,
326 JS::GCCellPtr aValue) override {
327 bool keyShouldBeBlack;
328 bool valueShouldBeBlack;
329 ShouldWeakMappingEntryBeBlack(aMap, aKey, aValue, &keyShouldBeBlack,
330 &valueShouldBeBlack);
332 if (keyShouldBeBlack) {
333 fprintf(stderr, "Weak mapping key %p of map %p should be black\n",
334 aKey.asCell(), aMap);
335 mFailed = true;
338 if (valueShouldBeBlack) {
339 fprintf(stderr, "Weak mapping value %p of map %p should be black\n",
340 aValue.asCell(), aMap);
341 mFailed = true;
345 bool mFailed;
347 #endif // DEBUG
349 static void CheckParticipatesInCycleCollection(JS::GCCellPtr aThing,
350 const char* aName,
351 void* aClosure) {
352 bool* cycleCollectionEnabled = static_cast<bool*>(aClosure);
354 if (*cycleCollectionEnabled) {
355 return;
358 if (JS::IsCCTraceKind(aThing.kind()) && JS::GCThingIsMarkedGrayInCC(aThing)) {
359 *cycleCollectionEnabled = true;
363 NS_IMETHODIMP
364 JSGCThingParticipant::TraverseNative(void* aPtr,
365 nsCycleCollectionTraversalCallback& aCb) {
366 auto runtime = reinterpret_cast<CycleCollectedJSRuntime*>(
367 reinterpret_cast<char*>(this) -
368 offsetof(CycleCollectedJSRuntime, mGCThingCycleCollectorGlobal));
370 JS::GCCellPtr cellPtr(aPtr, JS::GCThingTraceKind(aPtr));
371 runtime->TraverseGCThing(CycleCollectedJSRuntime::TRAVERSE_FULL, cellPtr,
372 aCb);
373 return NS_OK;
376 // NB: This is only used to initialize the participant in
377 // CycleCollectedJSRuntime. It should never be used directly.
378 static JSGCThingParticipant sGCThingCycleCollectorGlobal;
380 NS_IMETHODIMP
381 JSZoneParticipant::TraverseNative(void* aPtr,
382 nsCycleCollectionTraversalCallback& aCb) {
383 auto runtime = reinterpret_cast<CycleCollectedJSRuntime*>(
384 reinterpret_cast<char*>(this) -
385 offsetof(CycleCollectedJSRuntime, mJSZoneCycleCollectorGlobal));
387 MOZ_ASSERT(!aCb.WantAllTraces());
388 JS::Zone* zone = static_cast<JS::Zone*>(aPtr);
390 runtime->TraverseZone(zone, aCb);
391 return NS_OK;
394 struct TraversalTracer : public JS::CallbackTracer {
395 TraversalTracer(JSRuntime* aRt, nsCycleCollectionTraversalCallback& aCb)
396 : JS::CallbackTracer(aRt, JS::TracerKind::Callback,
397 JS::TraceOptions(JS::WeakMapTraceAction::Skip,
398 JS::WeakEdgeTraceAction::Trace)),
399 mCb(aCb) {}
400 void onChild(JS::GCCellPtr aThing, const char* name) override;
401 nsCycleCollectionTraversalCallback& mCb;
404 void TraversalTracer::onChild(JS::GCCellPtr aThing, const char* name) {
405 // Checking strings and symbols for being gray is rather slow, and we don't
406 // need either of them for the cycle collector.
407 if (aThing.is<JSString>() || aThing.is<JS::Symbol>()) {
408 return;
411 // Don't traverse non-gray objects, unless we want all traces.
412 if (!JS::GCThingIsMarkedGrayInCC(aThing) && !mCb.WantAllTraces()) {
413 return;
417 * This function needs to be careful to avoid stack overflow. Normally, when
418 * IsCCTraceKind is true, the recursion terminates immediately as we just add
419 * |thing| to the CC graph. So overflow is only possible when there are long
420 * or cyclic chains of non-IsCCTraceKind GC things. Places where this can
421 * occur use special APIs to handle such chains iteratively.
423 if (JS::IsCCTraceKind(aThing.kind())) {
424 if (MOZ_UNLIKELY(mCb.WantDebugInfo())) {
425 char buffer[200];
426 context().getEdgeName(name, buffer, sizeof(buffer));
427 mCb.NoteNextEdgeName(buffer);
429 mCb.NoteJSChild(aThing);
430 return;
433 // Allow re-use of this tracer inside trace callback.
434 JS::AutoClearTracingContext actc(this);
436 if (aThing.is<js::Shape>()) {
437 // The maximum depth of traversal when tracing a Shape is unbounded, due to
438 // the parent pointers on the shape.
439 JS_TraceShapeCycleCollectorChildren(this, aThing);
440 } else {
441 JS::TraceChildren(this, aThing);
446 * The cycle collection participant for a Zone is intended to produce the same
447 * results as if all of the gray GCthings in a zone were merged into a single
448 * node, except for self-edges. This avoids the overhead of representing all of
449 * the GCthings in the zone in the cycle collector graph, which should be much
450 * faster if many of the GCthings in the zone are gray.
452 * Zone merging should not always be used, because it is a conservative
453 * approximation of the true cycle collector graph that can incorrectly identify
454 * some garbage objects as being live. For instance, consider two cycles that
455 * pass through a zone, where one is garbage and the other is live. If we merge
456 * the entire zone, the cycle collector will think that both are alive.
458 * We don't have to worry about losing track of a garbage cycle, because any
459 * such garbage cycle incorrectly identified as live must contain at least one
460 * C++ to JS edge, and XPConnect will always add the C++ object to the CC graph.
461 * (This is in contrast to pure C++ garbage cycles, which must always be
462 * properly identified, because we clear the purple buffer during every CC,
463 * which may contain the last reference to a garbage cycle.)
466 // NB: This is only used to initialize the participant in
467 // CycleCollectedJSRuntime. It should never be used directly.
468 static const JSZoneParticipant sJSZoneCycleCollectorGlobal;
470 static void JSObjectsTenuredCb(JSContext* aContext, void* aData) {
471 static_cast<CycleCollectedJSRuntime*>(aData)->JSObjectsTenured();
474 static void MozCrashWarningReporter(JSContext*, JSErrorReport*) {
475 MOZ_CRASH("Why is someone touching JSAPI without an AutoJSAPI?");
478 JSHolderMap::Entry::Entry() : Entry(nullptr, nullptr, nullptr) {}
480 JSHolderMap::Entry::Entry(void* aHolder, nsScriptObjectTracer* aTracer,
481 JS::Zone* aZone)
482 : mHolder(aHolder),
483 mTracer(aTracer)
484 #ifdef DEBUG
486 mZone(aZone)
487 #endif
491 void JSHolderMap::EntryVectorIter::Settle() {
492 if (Done()) {
493 return;
496 Entry* entry = &mIter.Get();
498 // If the entry has been cleared, remove it and shrink the vector.
499 if (!entry->mHolder && !mHolderMap.RemoveEntry(mVector, entry)) {
500 // We removed the last entry, so reset the iterator to an empty one.
501 mIter = EntryVector().Iter();
502 MOZ_ASSERT(Done());
506 JSHolderMap::Iter::Iter(JSHolderMap& aMap, WhichHolders aWhich)
507 : mHolderMap(aMap), mIter(aMap, aMap.mAnyZoneJSHolders) {
508 MOZ_RELEASE_ASSERT(!mHolderMap.mHasIterator);
509 mHolderMap.mHasIterator = true;
511 // Populate vector of zones to iterate after the any-zone holders.
512 for (auto i = aMap.mPerZoneJSHolders.iter(); !i.done(); i.next()) {
513 JS::Zone* zone = i.get().key();
514 if (aWhich == AllHolders || JS::NeedGrayRootsForZone(i.get().key())) {
515 MOZ_ALWAYS_TRUE(mZones.append(zone));
519 Settle();
522 void JSHolderMap::Iter::Settle() {
523 while (mIter.Done()) {
524 if (mZone && mIter.Vector().IsEmpty()) {
525 mHolderMap.mPerZoneJSHolders.remove(mZone);
528 mZone = nullptr;
529 if (mZones.empty()) {
530 break;
533 mZone = mZones.popCopy();
534 EntryVector& vector = *mHolderMap.mPerZoneJSHolders.lookup(mZone)->value();
535 new (&mIter) EntryVectorIter(mHolderMap, vector);
539 void JSHolderMap::Iter::UpdateForRemovals() {
540 mIter.Settle();
541 Settle();
544 JSHolderMap::JSHolderMap() : mJSHolderMap(256) {}
546 bool JSHolderMap::RemoveEntry(EntryVector& aJSHolders, Entry* aEntry) {
547 MOZ_ASSERT(aEntry);
548 MOZ_ASSERT(!aEntry->mHolder);
550 // Remove all dead entries from the end of the vector.
551 while (!aJSHolders.GetLast().mHolder && &aJSHolders.GetLast() != aEntry) {
552 aJSHolders.PopLast();
555 // Swap the element we want to remove with the last one and update the hash
556 // table.
557 Entry* lastEntry = &aJSHolders.GetLast();
558 if (aEntry != lastEntry) {
559 MOZ_ASSERT(lastEntry->mHolder);
560 *aEntry = *lastEntry;
561 MOZ_ASSERT(mJSHolderMap.has(aEntry->mHolder));
562 MOZ_ALWAYS_TRUE(mJSHolderMap.put(aEntry->mHolder, aEntry));
565 aJSHolders.PopLast();
567 // Return whether aEntry is still in the vector.
568 return aEntry != lastEntry;
571 bool JSHolderMap::Has(void* aHolder) const { return mJSHolderMap.has(aHolder); }
573 nsScriptObjectTracer* JSHolderMap::Get(void* aHolder) const {
574 auto ptr = mJSHolderMap.lookup(aHolder);
575 if (!ptr) {
576 return nullptr;
579 Entry* entry = ptr->value();
580 MOZ_ASSERT(entry->mHolder == aHolder);
581 return entry->mTracer;
584 nsScriptObjectTracer* JSHolderMap::Extract(void* aHolder) {
585 MOZ_ASSERT(aHolder);
587 auto ptr = mJSHolderMap.lookup(aHolder);
588 if (!ptr) {
589 return nullptr;
592 Entry* entry = ptr->value();
593 MOZ_ASSERT(entry->mHolder == aHolder);
594 nsScriptObjectTracer* tracer = entry->mTracer;
596 // Clear the entry's contents. It will be removed the next time iteration
597 // visits this entry.
598 *entry = Entry();
600 mJSHolderMap.remove(ptr);
602 return tracer;
605 void JSHolderMap::Put(void* aHolder, nsScriptObjectTracer* aTracer,
606 JS::Zone* aZone) {
607 MOZ_ASSERT(aHolder);
608 MOZ_ASSERT(aTracer);
610 // Don't associate multi-zone holders with a zone, even if one is supplied.
611 if (!aTracer->IsSingleZoneJSHolder()) {
612 aZone = nullptr;
615 auto ptr = mJSHolderMap.lookupForAdd(aHolder);
616 if (ptr) {
617 Entry* entry = ptr->value();
618 #ifdef DEBUG
619 MOZ_ASSERT(entry->mHolder == aHolder);
620 MOZ_ASSERT(entry->mTracer == aTracer,
621 "Don't call HoldJSObjects in superclass ctors");
622 if (aZone) {
623 if (entry->mZone) {
624 MOZ_ASSERT(entry->mZone == aZone);
625 } else {
626 entry->mZone = aZone;
629 #endif
630 entry->mTracer = aTracer;
631 return;
634 EntryVector* vector = &mAnyZoneJSHolders;
635 if (aZone) {
636 auto ptr = mPerZoneJSHolders.lookupForAdd(aZone);
637 if (!ptr) {
638 MOZ_ALWAYS_TRUE(
639 mPerZoneJSHolders.add(ptr, aZone, MakeUnique<EntryVector>()));
641 vector = ptr->value().get();
644 vector->InfallibleAppend(Entry{aHolder, aTracer, aZone});
645 MOZ_ALWAYS_TRUE(mJSHolderMap.add(ptr, aHolder, &vector->GetLast()));
648 size_t JSHolderMap::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
649 size_t n = 0;
651 // We're deliberately not measuring anything hanging off the entries in
652 // mJSHolders.
653 n += mJSHolderMap.shallowSizeOfExcludingThis(aMallocSizeOf);
654 n += mAnyZoneJSHolders.SizeOfExcludingThis(aMallocSizeOf);
655 n += mPerZoneJSHolders.shallowSizeOfExcludingThis(aMallocSizeOf);
656 for (auto i = mPerZoneJSHolders.iter(); !i.done(); i.next()) {
657 n += i.get().value()->SizeOfExcludingThis(aMallocSizeOf);
660 return n;
663 static bool InitializeShadowRealm(JSContext* aCx,
664 JS::Handle<JSObject*> aGlobal) {
665 MOZ_ASSERT(StaticPrefs::javascript_options_experimental_shadow_realms());
667 JSAutoRealm ar(aCx, aGlobal);
668 return dom::RegisterShadowRealmBindings(aCx, aGlobal);
671 CycleCollectedJSRuntime::CycleCollectedJSRuntime(JSContext* aCx)
672 : mContext(nullptr),
673 mGCThingCycleCollectorGlobal(sGCThingCycleCollectorGlobal),
674 mJSZoneCycleCollectorGlobal(sJSZoneCycleCollectorGlobal),
675 mJSRuntime(JS_GetRuntime(aCx)),
676 mHasPendingIdleGCTask(false),
677 mPrevGCSliceCallback(nullptr),
678 mOutOfMemoryState(OOMState::OK),
679 mLargeAllocationFailureState(OOMState::OK)
680 #ifdef DEBUG
682 mShutdownCalled(false)
683 #endif
685 MOZ_COUNT_CTOR(CycleCollectedJSRuntime);
686 MOZ_ASSERT(aCx);
687 MOZ_ASSERT(mJSRuntime);
689 #if defined(XP_MACOSX)
690 if (!XRE_IsParentProcess()) {
691 nsMacUtilsImpl::EnableTCSMIfAvailable();
693 #endif
695 if (!JS_AddExtraGCRootsTracer(aCx, TraceBlackJS, this)) {
696 MOZ_CRASH("JS_AddExtraGCRootsTracer failed");
698 JS_SetGrayGCRootsTracer(aCx, TraceGrayJS, this);
699 JS_SetGCCallback(aCx, GCCallback, this);
700 mPrevGCSliceCallback = JS::SetGCSliceCallback(aCx, GCSliceCallback);
702 if (NS_IsMainThread()) {
703 // We would like to support all threads here, but the way timeline consumers
704 // are set up currently, you can either add a marker for one specific
705 // docshell, or for every consumer globally. We would like to add a marker
706 // for every consumer observing anything on this thread, but that is not
707 // currently possible. For now, add global markers only when we are on the
708 // main thread, since the UI for this tracing data only displays data
709 // relevant to the main-thread.
710 JS::AddGCNurseryCollectionCallback(aCx, GCNurseryCollectionCallback,
711 nullptr);
714 JS_SetObjectsTenuredCallback(aCx, JSObjectsTenuredCb, this);
715 JS::SetOutOfMemoryCallback(aCx, OutOfMemoryCallback, this);
716 JS::SetWaitCallback(mJSRuntime, BeforeWaitCallback, AfterWaitCallback,
717 sizeof(dom::AutoYieldJSThreadExecution));
718 JS::SetWarningReporter(aCx, MozCrashWarningReporter);
719 JS::SetShadowRealmInitializeGlobalCallback(aCx, InitializeShadowRealm);
720 JS::SetShadowRealmGlobalCreationCallback(aCx, dom::NewShadowRealmGlobal);
722 js::AutoEnterOOMUnsafeRegion::setAnnotateOOMAllocationSizeCallback(
723 CrashReporter::AnnotateOOMAllocationSize);
725 static js::DOMCallbacks DOMcallbacks = {InstanceClassHasProtoAtDepth};
726 SetDOMCallbacks(aCx, &DOMcallbacks);
727 js::SetScriptEnvironmentPreparer(aCx, &mEnvironmentPreparer);
729 JS::dbg::SetDebuggerMallocSizeOf(aCx, moz_malloc_size_of);
731 #ifdef MOZ_JS_DEV_ERROR_INTERCEPTOR
732 JS_SetErrorInterceptorCallback(mJSRuntime, &mErrorInterceptor);
733 #endif // MOZ_JS_DEV_ERROR_INTERCEPTOR
735 JS_SetDestroyZoneCallback(aCx, OnZoneDestroyed);
738 #ifdef NS_BUILD_REFCNT_LOGGING
739 class JSLeakTracer : public JS::CallbackTracer {
740 public:
741 explicit JSLeakTracer(JSRuntime* aRuntime)
742 : JS::CallbackTracer(aRuntime, JS::TracerKind::Callback,
743 JS::WeakMapTraceAction::TraceKeysAndValues) {}
745 private:
746 void onChild(JS::GCCellPtr thing, const char* name) override {
747 const char* kindName = JS::GCTraceKindToAscii(thing.kind());
748 size_t size = JS::GCTraceKindSize(thing.kind());
749 MOZ_LOG_CTOR(thing.asCell(), kindName, size);
752 #endif
754 void CycleCollectedJSRuntime::Shutdown(JSContext* aCx) {
755 #ifdef MOZ_JS_DEV_ERROR_INTERCEPTOR
756 mErrorInterceptor.Shutdown(mJSRuntime);
757 #endif // MOZ_JS_DEV_ERROR_INTERCEPTOR
759 // There should not be any roots left to trace at this point. Ensure any that
760 // remain are flagged as leaks.
761 #ifdef NS_BUILD_REFCNT_LOGGING
762 JSLeakTracer tracer(Runtime());
763 TraceNativeBlackRoots(&tracer);
764 TraceAllNativeGrayRoots(&tracer);
765 #endif
767 #ifdef DEBUG
768 mShutdownCalled = true;
769 #endif
771 JS_SetDestroyZoneCallback(aCx, nullptr);
773 if (NS_IsMainThread()) {
774 JS::RemoveGCNurseryCollectionCallback(aCx, GCNurseryCollectionCallback,
775 nullptr);
779 CycleCollectedJSRuntime::~CycleCollectedJSRuntime() {
780 MOZ_COUNT_DTOR(CycleCollectedJSRuntime);
781 MOZ_ASSERT(!mDeferredFinalizerTable.Count());
782 MOZ_ASSERT(!mFinalizeRunnable);
783 MOZ_ASSERT(mShutdownCalled);
786 void CycleCollectedJSRuntime::SetContext(CycleCollectedJSContext* aContext) {
787 MOZ_ASSERT(!mContext || !aContext, "Don't replace the context!");
788 mContext = aContext;
791 size_t CycleCollectedJSRuntime::SizeOfExcludingThis(
792 MallocSizeOf aMallocSizeOf) const {
793 return mJSHolders.SizeOfExcludingThis(aMallocSizeOf);
796 void CycleCollectedJSRuntime::UnmarkSkippableJSHolders() {
797 for (JSHolderMap::Iter entry(mJSHolders); !entry.Done(); entry.Next()) {
798 entry->mTracer->CanSkip(entry->mHolder, true);
802 void CycleCollectedJSRuntime::DescribeGCThing(
803 bool aIsMarked, JS::GCCellPtr aThing,
804 nsCycleCollectionTraversalCallback& aCb) const {
805 if (!aCb.WantDebugInfo()) {
806 aCb.DescribeGCedNode(aIsMarked, "JS Object");
807 return;
810 char name[72];
811 uint64_t compartmentAddress = 0;
812 if (aThing.is<JSObject>()) {
813 JSObject* obj = &aThing.as<JSObject>();
814 compartmentAddress = (uint64_t)JS::GetCompartment(obj);
815 const JSClass* clasp = JS::GetClass(obj);
817 // Give the subclass a chance to do something
818 if (DescribeCustomObjects(obj, clasp, name)) {
819 // Nothing else to do!
820 } else if (js::IsFunctionObject(obj)) {
821 JSFunction* fun = JS_GetObjectFunction(obj);
822 JSString* str = JS_GetMaybePartialFunctionDisplayId(fun);
823 if (str) {
824 JSLinearString* linear = JS_ASSERT_STRING_IS_LINEAR(str);
825 nsAutoString chars;
826 AssignJSLinearString(chars, linear);
827 NS_ConvertUTF16toUTF8 fname(chars);
828 SprintfLiteral(name, "JS Object (Function - %s)", fname.get());
829 } else {
830 SprintfLiteral(name, "JS Object (Function)");
832 } else {
833 SprintfLiteral(name, "JS Object (%s)", clasp->name);
835 } else {
836 SprintfLiteral(name, "%s", JS::GCTraceKindToAscii(aThing.kind()));
839 // Disable printing global for objects while we figure out ObjShrink fallout.
840 aCb.DescribeGCedNode(aIsMarked, name, compartmentAddress);
843 void CycleCollectedJSRuntime::NoteGCThingJSChildren(
844 JS::GCCellPtr aThing, nsCycleCollectionTraversalCallback& aCb) const {
845 TraversalTracer trc(mJSRuntime, aCb);
846 JS::TraceChildren(&trc, aThing);
849 void CycleCollectedJSRuntime::NoteGCThingXPCOMChildren(
850 const JSClass* aClasp, JSObject* aObj,
851 nsCycleCollectionTraversalCallback& aCb) const {
852 MOZ_ASSERT(aClasp);
853 MOZ_ASSERT(aClasp == JS::GetClass(aObj));
855 JS::Rooted<JSObject*> obj(RootingCx(), aObj);
857 if (NoteCustomGCThingXPCOMChildren(aClasp, obj, aCb)) {
858 // Nothing else to do!
859 return;
862 // XXX This test does seem fragile, we should probably allowlist classes
863 // that do hold a strong reference, but that might not be possible.
864 if (aClasp->slot0IsISupports()) {
865 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "JS::GetObjectISupports(obj)");
866 aCb.NoteXPCOMChild(JS::GetObjectISupports<nsISupports>(obj));
867 return;
870 const DOMJSClass* domClass = GetDOMClass(aClasp);
871 if (domClass) {
872 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "UnwrapDOMObject(obj)");
873 // It's possible that our object is an unforgeable holder object, in
874 // which case it doesn't actually have a C++ DOM object associated with
875 // it. Use UnwrapPossiblyNotInitializedDOMObject, which produces null in
876 // that case, since NoteXPCOMChild/NoteNativeChild are null-safe.
877 if (domClass->mDOMObjectIsISupports) {
878 aCb.NoteXPCOMChild(
879 UnwrapPossiblyNotInitializedDOMObject<nsISupports>(obj));
880 } else if (domClass->mParticipant) {
881 aCb.NoteNativeChild(UnwrapPossiblyNotInitializedDOMObject<void>(obj),
882 domClass->mParticipant);
884 return;
887 if (IsRemoteObjectProxy(obj)) {
888 auto handler =
889 static_cast<const RemoteObjectProxyBase*>(js::GetProxyHandler(obj));
890 return handler->NoteChildren(obj, aCb);
893 JS::Value value = js::MaybeGetScriptPrivate(obj);
894 if (!value.isUndefined()) {
895 aCb.NoteXPCOMChild(static_cast<nsISupports*>(value.toPrivate()));
899 void CycleCollectedJSRuntime::TraverseGCThing(
900 TraverseSelect aTs, JS::GCCellPtr aThing,
901 nsCycleCollectionTraversalCallback& aCb) {
902 bool isMarkedGray = JS::GCThingIsMarkedGrayInCC(aThing);
904 if (aTs == TRAVERSE_FULL) {
905 DescribeGCThing(!isMarkedGray, aThing, aCb);
908 // If this object is alive, then all of its children are alive. For JS
909 // objects, the black-gray invariant ensures the children are also marked
910 // black. For C++ objects, the ref count from this object will keep them
911 // alive. Thus we don't need to trace our children, unless we are debugging
912 // using WantAllTraces.
913 if (!isMarkedGray && !aCb.WantAllTraces()) {
914 return;
917 if (aTs == TRAVERSE_FULL) {
918 NoteGCThingJSChildren(aThing, aCb);
921 if (aThing.is<JSObject>()) {
922 JSObject* obj = &aThing.as<JSObject>();
923 NoteGCThingXPCOMChildren(JS::GetClass(obj), obj, aCb);
927 struct TraverseObjectShimClosure {
928 nsCycleCollectionTraversalCallback& cb;
929 CycleCollectedJSRuntime* self;
932 void CycleCollectedJSRuntime::TraverseZone(
933 JS::Zone* aZone, nsCycleCollectionTraversalCallback& aCb) {
935 * We treat the zone as being gray. We handle non-gray GCthings in the
936 * zone by not reporting their children to the CC. The black-gray invariant
937 * ensures that any JS children will also be non-gray, and thus don't need to
938 * be added to the graph. For C++ children, not representing the edge from the
939 * non-gray JS GCthings to the C++ object will keep the child alive.
941 * We don't allow zone merging in a WantAllTraces CC, because then these
942 * assumptions don't hold.
944 aCb.DescribeGCedNode(false, "JS Zone");
947 * Every JS child of everything in the zone is either in the zone
948 * or is a cross-compartment wrapper. In the former case, we don't need to
949 * represent these edges in the CC graph because JS objects are not ref
950 * counted. In the latter case, the JS engine keeps a map of these wrappers,
951 * which we iterate over. Edges between compartments in the same zone will add
952 * unnecessary loop edges to the graph (bug 842137).
954 TraversalTracer trc(mJSRuntime, aCb);
955 js::TraceGrayWrapperTargets(&trc, aZone);
958 * To find C++ children of things in the zone, we scan every JS Object in
959 * the zone. Only JS Objects can have C++ children.
961 TraverseObjectShimClosure closure = {aCb, this};
962 js::IterateGrayObjects(aZone, TraverseObjectShim, &closure);
965 /* static */
966 void CycleCollectedJSRuntime::TraverseObjectShim(
967 void* aData, JS::GCCellPtr aThing, const JS::AutoRequireNoGC& nogc) {
968 TraverseObjectShimClosure* closure =
969 static_cast<TraverseObjectShimClosure*>(aData);
971 MOZ_ASSERT(aThing.is<JSObject>());
972 closure->self->TraverseGCThing(CycleCollectedJSRuntime::TRAVERSE_CPP, aThing,
973 closure->cb);
976 void CycleCollectedJSRuntime::TraverseNativeRoots(
977 nsCycleCollectionNoteRootCallback& aCb) {
978 // NB: This is here just to preserve the existing XPConnect order. I doubt it
979 // would hurt to do this after the JS holders.
980 TraverseAdditionalNativeRoots(aCb);
982 for (JSHolderMap::Iter entry(mJSHolders); !entry.Done(); entry.Next()) {
983 void* holder = entry->mHolder;
984 nsScriptObjectTracer* tracer = entry->mTracer;
986 bool noteRoot = false;
987 if (MOZ_UNLIKELY(aCb.WantAllTraces())) {
988 noteRoot = true;
989 } else {
990 tracer->Trace(holder,
991 TraceCallbackFunc(CheckParticipatesInCycleCollection),
992 &noteRoot);
995 if (noteRoot) {
996 aCb.NoteNativeRoot(holder, tracer);
1001 /* static */
1002 void CycleCollectedJSRuntime::TraceBlackJS(JSTracer* aTracer, void* aData) {
1003 CycleCollectedJSRuntime* self = static_cast<CycleCollectedJSRuntime*>(aData);
1005 self->TraceNativeBlackRoots(aTracer);
1008 /* static */
1009 bool CycleCollectedJSRuntime::TraceGrayJS(JSTracer* aTracer,
1010 js::SliceBudget& budget,
1011 void* aData) {
1012 CycleCollectedJSRuntime* self = static_cast<CycleCollectedJSRuntime*>(aData);
1014 // Mark these roots as gray so the CC can walk them later.
1016 JSHolderMap::WhichHolders which = JSHolderMap::AllHolders;
1018 // Only trace holders in collecting zones when marking, except if we are
1019 // collecting the atoms zone since any holder may point into that zone.
1020 if (aTracer->isMarkingTracer() &&
1021 !JS::AtomsZoneIsCollecting(self->Runtime())) {
1022 which = JSHolderMap::HoldersRequiredForGrayMarking;
1025 return self->TraceNativeGrayRoots(aTracer, which, budget);
1028 /* static */
1029 void CycleCollectedJSRuntime::GCCallback(JSContext* aContext,
1030 JSGCStatus aStatus,
1031 JS::GCReason aReason, void* aData) {
1032 CycleCollectedJSRuntime* self = static_cast<CycleCollectedJSRuntime*>(aData);
1034 MOZ_ASSERT(CycleCollectedJSContext::Get()->Context() == aContext);
1035 MOZ_ASSERT(CycleCollectedJSContext::Get()->Runtime() == self);
1037 self->OnGC(aContext, aStatus, aReason);
1040 /* static */
1041 void CycleCollectedJSRuntime::GCSliceCallback(JSContext* aContext,
1042 JS::GCProgress aProgress,
1043 const JS::GCDescription& aDesc) {
1044 CycleCollectedJSRuntime* self = CycleCollectedJSRuntime::Get();
1045 MOZ_ASSERT(CycleCollectedJSContext::Get()->Context() == aContext);
1047 if (profiler_thread_is_being_profiled_for_markers()) {
1048 if (aProgress == JS::GC_CYCLE_END) {
1049 struct GCMajorMarker {
1050 static constexpr mozilla::Span<const char> MarkerTypeName() {
1051 return mozilla::MakeStringSpan("GCMajor");
1053 static void StreamJSONMarkerData(
1054 mozilla::baseprofiler::SpliceableJSONWriter& aWriter,
1055 const mozilla::ProfilerString8View& aTimingJSON) {
1056 if (aTimingJSON.Length() != 0) {
1057 aWriter.SplicedJSONProperty("timings", aTimingJSON);
1058 } else {
1059 aWriter.NullProperty("timings");
1062 static mozilla::MarkerSchema MarkerTypeDisplay() {
1063 using MS = mozilla::MarkerSchema;
1064 MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable,
1065 MS::Location::TimelineMemory};
1066 schema.AddStaticLabelValue(
1067 "Description",
1068 "Summary data for an entire major GC, encompassing a set of "
1069 "incremental slices. The main thread is not blocked for the "
1070 "entire major GC interval, only for the individual slices.");
1071 // No display instructions here, there is special handling in the
1072 // front-end.
1073 return schema;
1077 profiler_add_marker("GCMajor", baseprofiler::category::GCCC,
1078 MarkerTiming::Interval(aDesc.startTime(aContext),
1079 aDesc.endTime(aContext)),
1080 GCMajorMarker{},
1081 ProfilerString8View::WrapNullTerminatedString(
1082 aDesc.formatJSONProfiler(aContext).get()));
1083 } else if (aProgress == JS::GC_SLICE_END) {
1084 struct GCSliceMarker {
1085 static constexpr mozilla::Span<const char> MarkerTypeName() {
1086 return mozilla::MakeStringSpan("GCSlice");
1088 static void StreamJSONMarkerData(
1089 mozilla::baseprofiler::SpliceableJSONWriter& aWriter,
1090 const mozilla::ProfilerString8View& aTimingJSON) {
1091 if (aTimingJSON.Length() != 0) {
1092 aWriter.SplicedJSONProperty("timings", aTimingJSON);
1093 } else {
1094 aWriter.NullProperty("timings");
1097 static mozilla::MarkerSchema MarkerTypeDisplay() {
1098 using MS = mozilla::MarkerSchema;
1099 MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable,
1100 MS::Location::TimelineMemory};
1101 schema.AddStaticLabelValue(
1102 "Description",
1103 "One slice of an incremental garbage collection (GC). The main "
1104 "thread is blocked during this time.");
1105 // No display instructions here, there is special handling in the
1106 // front-end.
1107 return schema;
1111 profiler_add_marker("GCSlice", baseprofiler::category::GCCC,
1112 MarkerTiming::Interval(aDesc.lastSliceStart(aContext),
1113 aDesc.lastSliceEnd(aContext)),
1114 GCSliceMarker{},
1115 ProfilerString8View::WrapNullTerminatedString(
1116 aDesc.sliceToJSONProfiler(aContext).get()));
1120 if (aProgress == JS::GC_CYCLE_END &&
1121 JS::dbg::FireOnGarbageCollectionHookRequired(aContext)) {
1122 JS::GCReason reason = aDesc.reason_;
1123 Unused << NS_WARN_IF(
1124 NS_FAILED(DebuggerOnGCRunnable::Enqueue(aContext, aDesc)) &&
1125 reason != JS::GCReason::SHUTDOWN_CC &&
1126 reason != JS::GCReason::DESTROY_RUNTIME &&
1127 reason != JS::GCReason::XPCONNECT_SHUTDOWN);
1130 if (self->mPrevGCSliceCallback) {
1131 self->mPrevGCSliceCallback(aContext, aProgress, aDesc);
1135 /* static */
1136 void CycleCollectedJSRuntime::GCNurseryCollectionCallback(
1137 JSContext* aContext, JS::GCNurseryProgress aProgress, JS::GCReason aReason,
1138 void* data) {
1139 CycleCollectedJSRuntime* self = CycleCollectedJSRuntime::Get();
1140 MOZ_ASSERT(CycleCollectedJSContext::Get()->Context() == aContext);
1141 MOZ_ASSERT(NS_IsMainThread());
1143 TimeStamp now = TimeStamp::Now();
1144 if (aProgress == JS::GCNurseryProgress::GC_NURSERY_COLLECTION_START) {
1145 self->mLatestNurseryCollectionStart = now;
1146 } else if (aProgress == JS::GCNurseryProgress::GC_NURSERY_COLLECTION_END) {
1147 PerfStats::RecordMeasurement(PerfStats::Metric::MinorGC,
1148 now - self->mLatestNurseryCollectionStart);
1151 if (aProgress == JS::GCNurseryProgress::GC_NURSERY_COLLECTION_END &&
1152 profiler_thread_is_being_profiled_for_markers()) {
1153 struct GCMinorMarker {
1154 static constexpr mozilla::Span<const char> MarkerTypeName() {
1155 return mozilla::MakeStringSpan("GCMinor");
1157 static void StreamJSONMarkerData(
1158 mozilla::baseprofiler::SpliceableJSONWriter& aWriter,
1159 const mozilla::ProfilerString8View& aTimingJSON) {
1160 if (aTimingJSON.Length() != 0) {
1161 aWriter.SplicedJSONProperty("nursery", aTimingJSON);
1162 } else {
1163 aWriter.NullProperty("nursery");
1166 static mozilla::MarkerSchema MarkerTypeDisplay() {
1167 using MS = mozilla::MarkerSchema;
1168 MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable,
1169 MS::Location::TimelineMemory};
1170 schema.AddStaticLabelValue(
1171 "Description",
1172 "A minor GC (aka nursery collection) to clear out the buffer used "
1173 "for recent allocations and move surviving data to the tenured "
1174 "(long-lived) heap.");
1175 // No display instructions here, there is special handling in the
1176 // front-end.
1177 return schema;
1181 profiler_add_marker(
1182 "GCMinor", baseprofiler::category::GCCC,
1183 MarkerTiming::Interval(self->mLatestNurseryCollectionStart, now),
1184 GCMinorMarker{},
1185 ProfilerString8View::WrapNullTerminatedString(
1186 JS::MinorGcToJSON(aContext).get()));
1190 /* static */
1191 void CycleCollectedJSRuntime::OutOfMemoryCallback(JSContext* aContext,
1192 void* aData) {
1193 CycleCollectedJSRuntime* self = static_cast<CycleCollectedJSRuntime*>(aData);
1195 MOZ_ASSERT(CycleCollectedJSContext::Get()->Context() == aContext);
1196 MOZ_ASSERT(CycleCollectedJSContext::Get()->Runtime() == self);
1198 self->OnOutOfMemory();
1201 /* static */
1202 void* CycleCollectedJSRuntime::BeforeWaitCallback(uint8_t* aMemory) {
1203 MOZ_ASSERT(aMemory);
1205 // aMemory is stack allocated memory to contain our RAII object. This allows
1206 // for us to avoid allocations on the heap during this callback.
1207 return new (aMemory) dom::AutoYieldJSThreadExecution;
1210 /* static */
1211 void CycleCollectedJSRuntime::AfterWaitCallback(void* aCookie) {
1212 MOZ_ASSERT(aCookie);
1213 static_cast<dom::AutoYieldJSThreadExecution*>(aCookie)
1214 ->~AutoYieldJSThreadExecution();
1217 struct JsGcTracer : public TraceCallbacks {
1218 virtual void Trace(JS::Heap<JS::Value>* aPtr, const char* aName,
1219 void* aClosure) const override {
1220 JS::TraceEdge(static_cast<JSTracer*>(aClosure), aPtr, aName);
1222 virtual void Trace(JS::Heap<jsid>* aPtr, const char* aName,
1223 void* aClosure) const override {
1224 JS::TraceEdge(static_cast<JSTracer*>(aClosure), aPtr, aName);
1226 virtual void Trace(JS::Heap<JSObject*>* aPtr, const char* aName,
1227 void* aClosure) const override {
1228 JS::TraceEdge(static_cast<JSTracer*>(aClosure), aPtr, aName);
1230 virtual void Trace(nsWrapperCache* aPtr, const char* aName,
1231 void* aClosure) const override {
1232 aPtr->TraceWrapper(static_cast<JSTracer*>(aClosure), aName);
1234 virtual void Trace(JS::TenuredHeap<JSObject*>* aPtr, const char* aName,
1235 void* aClosure) const override {
1236 JS::TraceEdge(static_cast<JSTracer*>(aClosure), aPtr, aName);
1238 virtual void Trace(JS::Heap<JSString*>* aPtr, const char* aName,
1239 void* aClosure) const override {
1240 JS::TraceEdge(static_cast<JSTracer*>(aClosure), aPtr, aName);
1242 virtual void Trace(JS::Heap<JSScript*>* aPtr, const char* aName,
1243 void* aClosure) const override {
1244 JS::TraceEdge(static_cast<JSTracer*>(aClosure), aPtr, aName);
1246 virtual void Trace(JS::Heap<JSFunction*>* aPtr, const char* aName,
1247 void* aClosure) const override {
1248 JS::TraceEdge(static_cast<JSTracer*>(aClosure), aPtr, aName);
1252 void mozilla::TraceScriptHolder(nsISupports* aHolder, JSTracer* aTracer) {
1253 nsXPCOMCycleCollectionParticipant* participant = nullptr;
1254 CallQueryInterface(aHolder, &participant);
1255 participant->Trace(aHolder, JsGcTracer(), aTracer);
1258 #if defined(NIGHTLY_BUILD) || defined(MOZ_DEV_EDITION) || defined(DEBUG)
1259 # define CHECK_SINGLE_ZONE_JS_HOLDERS
1260 #endif
1262 #ifdef CHECK_SINGLE_ZONE_JS_HOLDERS
1264 // A tracer that checks that a JS holder only holds JS GC things in a single
1265 // JS::Zone.
1266 struct CheckZoneTracer : public TraceCallbacks {
1267 const char* mClassName;
1268 mutable JS::Zone* mZone;
1270 explicit CheckZoneTracer(const char* aClassName, JS::Zone* aZone = nullptr)
1271 : mClassName(aClassName), mZone(aZone) {}
1273 void checkZone(JS::Zone* aZone, const char* aName) const {
1274 if (JS::IsAtomsZone(aZone)) {
1275 // Any holder may contain pointers into the atoms zone.
1276 return;
1279 if (!mZone) {
1280 mZone = aZone;
1281 return;
1284 if (aZone == mZone) {
1285 return;
1288 // Most JS holders only contain pointers to GC things in a single zone. We
1289 // group holders by referent zone where possible, allowing us to improve GC
1290 // performance by only tracing holders for zones that are being collected.
1292 // Additionally, pointers from any holder into the atoms zone are allowed
1293 // since all holders are traced when we collect the atoms zone.
1295 // If you added a holder that has pointers into multiple zones do not
1296 // use NS_IMPL_CYCLE_COLLECTION_SINGLE_ZONE_SCRIPT_HOLDER_CLASS.
1297 MOZ_CRASH_UNSAFE_PRINTF(
1298 "JS holder %s contains pointers to GC things in more than one zone ("
1299 "found in %s)\n",
1300 mClassName, aName);
1303 virtual void Trace(JS::Heap<JS::Value>* aPtr, const char* aName,
1304 void* aClosure) const override {
1305 JS::Value value = aPtr->unbarrieredGet();
1306 if (value.isGCThing()) {
1307 checkZone(JS::GetGCThingZone(value.toGCCellPtr()), aName);
1310 virtual void Trace(JS::Heap<jsid>* aPtr, const char* aName,
1311 void* aClosure) const override {
1312 jsid id = aPtr->unbarrieredGet();
1313 if (id.isGCThing()) {
1314 MOZ_ASSERT(JS::IsAtomsZone(JS::GetTenuredGCThingZone(id.toGCCellPtr())));
1317 virtual void Trace(JS::Heap<JSObject*>* aPtr, const char* aName,
1318 void* aClosure) const override {
1319 JSObject* obj = aPtr->unbarrieredGet();
1320 if (obj) {
1321 checkZone(js::GetObjectZoneFromAnyThread(obj), aName);
1324 virtual void Trace(nsWrapperCache* aPtr, const char* aName,
1325 void* aClosure) const override {
1326 JSObject* obj = aPtr->GetWrapperPreserveColor();
1327 if (obj) {
1328 checkZone(js::GetObjectZoneFromAnyThread(obj), aName);
1331 virtual void Trace(JS::TenuredHeap<JSObject*>* aPtr, const char* aName,
1332 void* aClosure) const override {
1333 JSObject* obj = aPtr->unbarrieredGetPtr();
1334 if (obj) {
1335 checkZone(js::GetObjectZoneFromAnyThread(obj), aName);
1338 virtual void Trace(JS::Heap<JSString*>* aPtr, const char* aName,
1339 void* aClosure) const override {
1340 JSString* str = aPtr->unbarrieredGet();
1341 if (str) {
1342 checkZone(JS::GetStringZone(str), aName);
1345 virtual void Trace(JS::Heap<JSScript*>* aPtr, const char* aName,
1346 void* aClosure) const override {
1347 JSScript* script = aPtr->unbarrieredGet();
1348 if (script) {
1349 checkZone(JS::GetTenuredGCThingZone(JS::GCCellPtr(script)), aName);
1352 virtual void Trace(JS::Heap<JSFunction*>* aPtr, const char* aName,
1353 void* aClosure) const override {
1354 JSFunction* fun = aPtr->unbarrieredGet();
1355 if (fun) {
1356 checkZone(js::GetObjectZoneFromAnyThread(JS_GetFunctionObject(fun)),
1357 aName);
1362 static inline void CheckHolderIsSingleZone(
1363 void* aHolder, nsCycleCollectionParticipant* aParticipant,
1364 JS::Zone* aZone) {
1365 CheckZoneTracer tracer(aParticipant->ClassName(), aZone);
1366 aParticipant->Trace(aHolder, tracer, nullptr);
1369 #endif
1371 static inline bool ShouldCheckSingleZoneHolders() {
1372 #if defined(DEBUG)
1373 return true;
1374 #elif defined(NIGHTLY_BUILD) || defined(MOZ_DEV_EDITION)
1375 // Don't check every time to avoid performance impact.
1376 return rand() % 256 == 0;
1377 #else
1378 return false;
1379 #endif
1382 #ifdef NS_BUILD_REFCNT_LOGGING
1383 void CycleCollectedJSRuntime::TraceAllNativeGrayRoots(JSTracer* aTracer) {
1384 MOZ_RELEASE_ASSERT(mHolderIter.isNothing());
1385 js::SliceBudget budget = js::SliceBudget::unlimited();
1386 MOZ_ALWAYS_TRUE(
1387 TraceNativeGrayRoots(aTracer, JSHolderMap::AllHolders, budget));
1389 #endif
1391 bool CycleCollectedJSRuntime::TraceNativeGrayRoots(
1392 JSTracer* aTracer, JSHolderMap::WhichHolders aWhich,
1393 js::SliceBudget& aBudget) {
1394 if (!mHolderIter) {
1395 // NB: This is here just to preserve the existing XPConnect order. I doubt
1396 // it would hurt to do this after the JS holders.
1397 TraceAdditionalNativeGrayRoots(aTracer);
1399 mHolderIter.emplace(mJSHolders, aWhich);
1400 aBudget.stepAndForceCheck();
1401 } else {
1402 // Holders may have been removed between slices, so we may need to update
1403 // the iterator.
1404 mHolderIter->UpdateForRemovals();
1407 bool finished = TraceJSHolders(aTracer, *mHolderIter, aBudget);
1408 if (finished) {
1409 mHolderIter.reset();
1412 return finished;
1415 bool CycleCollectedJSRuntime::TraceJSHolders(JSTracer* aTracer,
1416 JSHolderMap::Iter& aIter,
1417 js::SliceBudget& aBudget) {
1418 bool checkSingleZoneHolders = ShouldCheckSingleZoneHolders();
1420 while (!aIter.Done() && !aBudget.isOverBudget()) {
1421 void* holder = aIter->mHolder;
1422 nsScriptObjectTracer* tracer = aIter->mTracer;
1424 #ifdef CHECK_SINGLE_ZONE_JS_HOLDERS
1425 if (checkSingleZoneHolders && tracer->IsSingleZoneJSHolder()) {
1426 CheckHolderIsSingleZone(holder, tracer, aIter.Zone());
1428 #else
1429 Unused << checkSingleZoneHolders;
1430 #endif
1432 tracer->Trace(holder, JsGcTracer(), aTracer);
1434 aIter.Next();
1435 aBudget.step();
1438 return aIter.Done();
1441 void CycleCollectedJSRuntime::AddJSHolder(void* aHolder,
1442 nsScriptObjectTracer* aTracer,
1443 JS::Zone* aZone) {
1444 mJSHolders.Put(aHolder, aTracer, aZone);
1447 struct ClearJSHolder : public TraceCallbacks {
1448 virtual void Trace(JS::Heap<JS::Value>* aPtr, const char*,
1449 void*) const override {
1450 aPtr->setUndefined();
1453 virtual void Trace(JS::Heap<jsid>* aPtr, const char*, void*) const override {
1454 *aPtr = JS::PropertyKey::Void();
1457 virtual void Trace(JS::Heap<JSObject*>* aPtr, const char*,
1458 void*) const override {
1459 *aPtr = nullptr;
1462 virtual void Trace(nsWrapperCache* aPtr, const char* aName,
1463 void* aClosure) const override {
1464 aPtr->ClearWrapper();
1467 virtual void Trace(JS::TenuredHeap<JSObject*>* aPtr, const char*,
1468 void*) const override {
1469 *aPtr = nullptr;
1472 virtual void Trace(JS::Heap<JSString*>* aPtr, const char*,
1473 void*) const override {
1474 *aPtr = nullptr;
1477 virtual void Trace(JS::Heap<JSScript*>* aPtr, const char*,
1478 void*) const override {
1479 *aPtr = nullptr;
1482 virtual void Trace(JS::Heap<JSFunction*>* aPtr, const char*,
1483 void*) const override {
1484 *aPtr = nullptr;
1488 void CycleCollectedJSRuntime::RemoveJSHolder(void* aHolder) {
1489 nsScriptObjectTracer* tracer = mJSHolders.Extract(aHolder);
1490 if (tracer) {
1491 // Bug 1531951: The analysis can't see through the virtual call but we know
1492 // that the ClearJSHolder tracer will never GC.
1493 JS::AutoSuppressGCAnalysis nogc;
1494 tracer->Trace(aHolder, ClearJSHolder(), nullptr);
1498 #ifdef DEBUG
1499 static void AssertNoGcThing(JS::GCCellPtr aGCThing, const char* aName,
1500 void* aClosure) {
1501 MOZ_ASSERT(!aGCThing);
1504 void CycleCollectedJSRuntime::AssertNoObjectsToTrace(void* aPossibleJSHolder) {
1505 nsScriptObjectTracer* tracer = mJSHolders.Get(aPossibleJSHolder);
1506 if (tracer) {
1507 tracer->Trace(aPossibleJSHolder, TraceCallbackFunc(AssertNoGcThing),
1508 nullptr);
1511 #endif
1513 nsCycleCollectionParticipant* CycleCollectedJSRuntime::GCThingParticipant() {
1514 return &mGCThingCycleCollectorGlobal;
1517 nsCycleCollectionParticipant* CycleCollectedJSRuntime::ZoneParticipant() {
1518 return &mJSZoneCycleCollectorGlobal;
1521 nsresult CycleCollectedJSRuntime::TraverseRoots(
1522 nsCycleCollectionNoteRootCallback& aCb) {
1523 TraverseNativeRoots(aCb);
1525 NoteWeakMapsTracer trc(mJSRuntime, aCb);
1526 js::TraceWeakMaps(&trc);
1528 return NS_OK;
1531 bool CycleCollectedJSRuntime::UsefulToMergeZones() const { return false; }
1533 void CycleCollectedJSRuntime::FixWeakMappingGrayBits() const {
1534 MOZ_ASSERT(!JS::IsIncrementalGCInProgress(mJSRuntime),
1535 "Don't call FixWeakMappingGrayBits during a GC.");
1536 FixWeakMappingGrayBitsTracer fixer(mJSRuntime);
1537 fixer.FixAll();
1540 void CycleCollectedJSRuntime::CheckGrayBits() const {
1541 MOZ_ASSERT(!JS::IsIncrementalGCInProgress(mJSRuntime),
1542 "Don't call CheckGrayBits during a GC.");
1544 #ifndef ANDROID
1545 // Bug 1346874 - The gray state check is expensive. Android tests are already
1546 // slow enough that this check can easily push them over the threshold to a
1547 // timeout.
1549 MOZ_ASSERT(js::CheckGrayMarkingState(mJSRuntime));
1550 MOZ_ASSERT(CheckWeakMappingGrayBitsTracer::Check(mJSRuntime));
1551 #endif
1554 bool CycleCollectedJSRuntime::AreGCGrayBitsValid() const {
1555 return js::AreGCGrayBitsValid(mJSRuntime);
1558 void CycleCollectedJSRuntime::GarbageCollect(JS::GCOptions aOptions,
1559 JS::GCReason aReason) const {
1560 JSContext* cx = CycleCollectedJSContext::Get()->Context();
1561 JS::PrepareForFullGC(cx);
1562 JS::NonIncrementalGC(cx, aOptions, aReason);
1565 void CycleCollectedJSRuntime::JSObjectsTenured() {
1566 JSContext* cx = CycleCollectedJSContext::Get()->Context();
1567 for (auto iter = mNurseryObjects.Iter(); !iter.Done(); iter.Next()) {
1568 nsWrapperCache* cache = iter.Get();
1569 JSObject* wrapper = cache->GetWrapperMaybeDead();
1570 MOZ_DIAGNOSTIC_ASSERT(wrapper);
1571 if (!JS::ObjectIsTenured(wrapper)) {
1572 MOZ_ASSERT(!cache->PreservingWrapper());
1573 js::gc::FinalizeDeadNurseryObject(cx, wrapper);
1577 mNurseryObjects.Clear();
1580 void CycleCollectedJSRuntime::NurseryWrapperAdded(nsWrapperCache* aCache) {
1581 MOZ_ASSERT(aCache);
1582 MOZ_ASSERT(aCache->GetWrapperMaybeDead());
1583 MOZ_ASSERT(!JS::ObjectIsTenured(aCache->GetWrapperMaybeDead()));
1584 mNurseryObjects.InfallibleAppend(aCache);
1587 void CycleCollectedJSRuntime::DeferredFinalize(
1588 DeferredFinalizeAppendFunction aAppendFunc, DeferredFinalizeFunction aFunc,
1589 void* aThing) {
1590 // Tell the analysis that the function pointers will not GC.
1591 JS::AutoSuppressGCAnalysis suppress;
1592 mDeferredFinalizerTable.WithEntryHandle(aFunc, [&](auto&& entry) {
1593 if (entry) {
1594 aAppendFunc(entry.Data(), aThing);
1595 } else {
1596 entry.Insert(aAppendFunc(nullptr, aThing));
1601 void CycleCollectedJSRuntime::DeferredFinalize(nsISupports* aSupports) {
1602 typedef DeferredFinalizerImpl<nsISupports> Impl;
1603 DeferredFinalize(Impl::AppendDeferredFinalizePointer, Impl::DeferredFinalize,
1604 aSupports);
1607 void CycleCollectedJSRuntime::DumpJSHeap(FILE* aFile) {
1608 JSContext* cx = CycleCollectedJSContext::Get()->Context();
1610 mozilla::MallocSizeOf mallocSizeOf =
1611 PR_GetEnv("MOZ_GC_LOG_SIZE") ? moz_malloc_size_of : nullptr;
1612 js::DumpHeap(cx, aFile, js::CollectNurseryBeforeDump, mallocSizeOf);
1615 IncrementalFinalizeRunnable::IncrementalFinalizeRunnable(
1616 CycleCollectedJSRuntime* aRt, DeferredFinalizerTable& aFinalizers)
1617 : DiscardableRunnable("IncrementalFinalizeRunnable"),
1618 mRuntime(aRt),
1619 mFinalizeFunctionToRun(0),
1620 mReleasing(false) {
1621 for (auto iter = aFinalizers.Iter(); !iter.Done(); iter.Next()) {
1622 DeferredFinalizeFunction& function = iter.Key();
1623 void*& data = iter.Data();
1625 DeferredFinalizeFunctionHolder* holder =
1626 mDeferredFinalizeFunctions.AppendElement();
1627 holder->run = function;
1628 holder->data = data;
1630 iter.Remove();
1632 MOZ_ASSERT(mDeferredFinalizeFunctions.Length());
1635 IncrementalFinalizeRunnable::~IncrementalFinalizeRunnable() {
1636 MOZ_ASSERT(!mDeferredFinalizeFunctions.Length());
1637 MOZ_ASSERT(!mRuntime);
1640 void IncrementalFinalizeRunnable::ReleaseNow(bool aLimited) {
1641 if (mReleasing) {
1642 NS_WARNING("Re-entering ReleaseNow");
1643 return;
1646 AUTO_PROFILER_LABEL("IncrementalFinalizeRunnable::ReleaseNow",
1647 GCCC_Finalize);
1649 mozilla::AutoRestore<bool> ar(mReleasing);
1650 mReleasing = true;
1651 MOZ_ASSERT(mDeferredFinalizeFunctions.Length() != 0,
1652 "We should have at least ReleaseSliceNow to run");
1653 MOZ_ASSERT(mFinalizeFunctionToRun < mDeferredFinalizeFunctions.Length(),
1654 "No more finalizers to run?");
1656 TimeDuration sliceTime = TimeDuration::FromMilliseconds(SliceMillis);
1657 TimeStamp started = aLimited ? TimeStamp::Now() : TimeStamp();
1658 bool timeout = false;
1659 do {
1660 const DeferredFinalizeFunctionHolder& function =
1661 mDeferredFinalizeFunctions[mFinalizeFunctionToRun];
1662 if (aLimited) {
1663 bool done = false;
1664 while (!timeout && !done) {
1666 * We don't want to read the clock too often, so we try to
1667 * release slices of 100 items.
1669 done = function.run(100, function.data);
1670 timeout = TimeStamp::Now() - started >= sliceTime;
1672 if (done) {
1673 ++mFinalizeFunctionToRun;
1675 if (timeout) {
1676 break;
1678 } else {
1679 while (!function.run(UINT32_MAX, function.data))
1681 ++mFinalizeFunctionToRun;
1683 } while (mFinalizeFunctionToRun < mDeferredFinalizeFunctions.Length());
1686 if (mFinalizeFunctionToRun == mDeferredFinalizeFunctions.Length()) {
1687 MOZ_ASSERT(mRuntime->mFinalizeRunnable == this);
1688 mDeferredFinalizeFunctions.Clear();
1689 CycleCollectedJSRuntime* runtime = mRuntime;
1690 mRuntime = nullptr;
1691 // NB: This may delete this!
1692 runtime->mFinalizeRunnable = nullptr;
1696 NS_IMETHODIMP
1697 IncrementalFinalizeRunnable::Run() {
1698 if (!mDeferredFinalizeFunctions.Length()) {
1699 /* These items were already processed synchronously in JSGC_END. */
1700 MOZ_ASSERT(!mRuntime);
1701 return NS_OK;
1704 MOZ_ASSERT(mRuntime->mFinalizeRunnable == this);
1705 TimeStamp start = TimeStamp::Now();
1706 ReleaseNow(true);
1708 if (mDeferredFinalizeFunctions.Length()) {
1709 nsresult rv = NS_DispatchToCurrentThread(this);
1710 if (NS_FAILED(rv)) {
1711 ReleaseNow(false);
1713 } else {
1714 MOZ_ASSERT(!mRuntime);
1717 uint32_t duration = (uint32_t)((TimeStamp::Now() - start).ToMilliseconds());
1718 Telemetry::Accumulate(Telemetry::DEFERRED_FINALIZE_ASYNC, duration);
1720 return NS_OK;
1723 void CycleCollectedJSRuntime::FinalizeDeferredThings(
1724 DeferredFinalizeType aType) {
1725 // If mFinalizeRunnable isn't null, we didn't finalize everything from the
1726 // previous GC.
1727 if (mFinalizeRunnable) {
1728 if (aType == FinalizeLater) {
1729 // We need to defer all finalization until we return to the event loop,
1730 // so leave things alone. Any new objects to be finalized from the current
1731 // GC will be handled by the existing mFinalizeRunnable.
1732 return;
1734 MOZ_ASSERT(aType == FinalizeIncrementally || aType == FinalizeNow);
1735 // If we're finalizing incrementally, we don't want finalizers to build up,
1736 // so try to finish them off now.
1737 // If we're finalizing synchronously, also go ahead and clear them out,
1738 // so we make sure as much as possible is freed.
1739 mFinalizeRunnable->ReleaseNow(false);
1740 if (mFinalizeRunnable) {
1741 // If we re-entered ReleaseNow, we couldn't delete mFinalizeRunnable and
1742 // we need to just continue processing it.
1743 return;
1747 // If there's nothing to finalize, don't create a new runnable.
1748 if (mDeferredFinalizerTable.Count() == 0) {
1749 return;
1752 mFinalizeRunnable =
1753 new IncrementalFinalizeRunnable(this, mDeferredFinalizerTable);
1755 // Everything should be gone now.
1756 MOZ_ASSERT(mDeferredFinalizerTable.Count() == 0);
1758 if (aType == FinalizeNow) {
1759 mFinalizeRunnable->ReleaseNow(false);
1760 MOZ_ASSERT(!mFinalizeRunnable);
1761 } else {
1762 MOZ_ASSERT(aType == FinalizeIncrementally || aType == FinalizeLater);
1763 NS_DispatchToCurrentThreadQueue(do_AddRef(mFinalizeRunnable), 2500,
1764 EventQueuePriority::Idle);
1768 const char* CycleCollectedJSRuntime::OOMStateToString(
1769 const OOMState aOomState) const {
1770 switch (aOomState) {
1771 case OOMState::OK:
1772 return "OK";
1773 case OOMState::Reporting:
1774 return "Reporting";
1775 case OOMState::Reported:
1776 return "Reported";
1777 case OOMState::Recovered:
1778 return "Recovered";
1779 default:
1780 MOZ_ASSERT_UNREACHABLE("OOMState holds an invalid value");
1781 return "Unknown";
1785 bool CycleCollectedJSRuntime::OOMReported() {
1786 return mOutOfMemoryState == OOMState::Reported;
1789 void CycleCollectedJSRuntime::AnnotateAndSetOutOfMemory(OOMState* aStatePtr,
1790 OOMState aNewState) {
1791 *aStatePtr = aNewState;
1792 CrashReporter::Annotation annotation =
1793 (aStatePtr == &mOutOfMemoryState)
1794 ? CrashReporter::Annotation::JSOutOfMemory
1795 : CrashReporter::Annotation::JSLargeAllocationFailure;
1797 CrashReporter::AnnotateCrashReport(
1798 annotation, nsDependentCString(OOMStateToString(aNewState)));
1801 void CycleCollectedJSRuntime::OnGC(JSContext* aContext, JSGCStatus aStatus,
1802 JS::GCReason aReason) {
1803 switch (aStatus) {
1804 case JSGC_BEGIN:
1805 MOZ_RELEASE_ASSERT(mHolderIter.isNothing());
1806 nsCycleCollector_prepareForGarbageCollection();
1807 PrepareWaitingZonesForGC();
1808 break;
1809 case JSGC_END: {
1810 MOZ_RELEASE_ASSERT(mHolderIter.isNothing());
1811 if (mOutOfMemoryState == OOMState::Reported) {
1812 AnnotateAndSetOutOfMemory(&mOutOfMemoryState, OOMState::Recovered);
1814 if (mLargeAllocationFailureState == OOMState::Reported) {
1815 AnnotateAndSetOutOfMemory(&mLargeAllocationFailureState,
1816 OOMState::Recovered);
1819 DeferredFinalizeType finalizeType;
1820 if (JS_IsExceptionPending(aContext)) {
1821 // There is a pending exception. The finalizers are not set up to run
1822 // in that state, so don't run the finalizer until we've returned to the
1823 // event loop.
1824 finalizeType = FinalizeLater;
1825 } else if (JS::InternalGCReason(aReason)) {
1826 if (aReason == JS::GCReason::DESTROY_RUNTIME) {
1827 // We're shutting down, so we need to destroy things immediately.
1828 finalizeType = FinalizeNow;
1829 } else {
1830 // We may be in the middle of running some code that the JIT has
1831 // assumed can't have certain kinds of side effects. Finalizers can do
1832 // all sorts of things, such as run JS, so we want to run them later,
1833 // after we've returned to the event loop.
1834 finalizeType = FinalizeLater;
1836 } else if (JS::WasIncrementalGC(mJSRuntime)) {
1837 // The GC was incremental, so we probably care about pauses. Try to
1838 // break up finalization, but it is okay if we do some now.
1839 finalizeType = FinalizeIncrementally;
1840 } else {
1841 // If we're running a synchronous GC, we probably want to free things as
1842 // quickly as possible. This can happen during testing or if memory is
1843 // low.
1844 finalizeType = FinalizeNow;
1846 FinalizeDeferredThings(finalizeType);
1848 break;
1850 default:
1851 MOZ_CRASH();
1854 CustomGCCallback(aStatus);
1857 void CycleCollectedJSRuntime::OnOutOfMemory() {
1858 AnnotateAndSetOutOfMemory(&mOutOfMemoryState, OOMState::Reporting);
1859 CustomOutOfMemoryCallback();
1860 AnnotateAndSetOutOfMemory(&mOutOfMemoryState, OOMState::Reported);
1863 void CycleCollectedJSRuntime::SetLargeAllocationFailure(OOMState aNewState) {
1864 AnnotateAndSetOutOfMemory(&mLargeAllocationFailureState, aNewState);
1867 void CycleCollectedJSRuntime::PrepareWaitingZonesForGC() {
1868 JSContext* cx = CycleCollectedJSContext::Get()->Context();
1869 if (mZonesWaitingForGC.Count() == 0) {
1870 JS::PrepareForFullGC(cx);
1871 } else {
1872 for (const auto& key : mZonesWaitingForGC) {
1873 JS::PrepareZoneForGC(cx, key);
1875 mZonesWaitingForGC.Clear();
1879 /* static */
1880 void CycleCollectedJSRuntime::OnZoneDestroyed(JS::GCContext* aGcx,
1881 JS::Zone* aZone) {
1882 // Remove the zone from the set of zones waiting for GC, if present. This can
1883 // happen if a zone is added to the set during an incremental GC in which it
1884 // is later destroyed.
1885 CycleCollectedJSRuntime* runtime = Get();
1886 runtime->mZonesWaitingForGC.Remove(aZone);
1889 void CycleCollectedJSRuntime::EnvironmentPreparer::invoke(
1890 JS::HandleObject global, js::ScriptEnvironmentPreparer::Closure& closure) {
1891 MOZ_ASSERT(JS_IsGlobalObject(global));
1892 nsIGlobalObject* nativeGlobal = xpc::NativeGlobal(global);
1894 // Not much we can do if we simply don't have a usable global here...
1895 NS_ENSURE_TRUE_VOID(nativeGlobal && nativeGlobal->HasJSGlobal());
1897 AutoEntryScript aes(nativeGlobal, "JS-engine-initiated execution");
1899 MOZ_ASSERT(!JS_IsExceptionPending(aes.cx()));
1901 DebugOnly<bool> ok = closure(aes.cx());
1903 MOZ_ASSERT_IF(ok, !JS_IsExceptionPending(aes.cx()));
1905 // The AutoEntryScript will check for JS_IsExceptionPending on the
1906 // JSContext and report it as needed as it comes off the stack.
1909 /* static */
1910 CycleCollectedJSRuntime* CycleCollectedJSRuntime::Get() {
1911 auto context = CycleCollectedJSContext::Get();
1912 if (context) {
1913 return context->Runtime();
1915 return nullptr;
1918 #ifdef MOZ_JS_DEV_ERROR_INTERCEPTOR
1920 namespace js {
1921 extern void DumpValue(const JS::Value& val);
1924 void CycleCollectedJSRuntime::ErrorInterceptor::Shutdown(JSRuntime* rt) {
1925 JS_SetErrorInterceptorCallback(rt, nullptr);
1926 mThrownError.reset();
1929 /* virtual */
1930 void CycleCollectedJSRuntime::ErrorInterceptor::interceptError(
1931 JSContext* cx, JS::HandleValue exn) {
1932 if (mThrownError) {
1933 // We already have an error, we don't need anything more.
1934 return;
1937 if (!nsContentUtils::ThreadsafeIsSystemCaller(cx)) {
1938 // We are only interested in chrome code.
1939 return;
1942 const auto type = JS_GetErrorType(exn);
1943 if (!type) {
1944 // This is not one of the primitive error types.
1945 return;
1948 switch (*type) {
1949 case JSExnType::JSEXN_REFERENCEERR:
1950 case JSExnType::JSEXN_SYNTAXERR:
1951 break;
1952 default:
1953 // Not one of the errors we are interested in.
1954 // Note that we are not interested in instances of `TypeError`
1955 // for the time being, as DOM (ab)uses this constructor to represent
1956 // all sorts of errors that are not even remotely related to type
1957 // errors (e.g. some network errors).
1958 // If we ever have a mechanism to differentiate between DOM-thrown
1959 // and SpiderMonkey-thrown instances of `TypeError`, we should
1960 // consider watching for `TypeError` here.
1961 return;
1964 // Now copy the details of the exception locally.
1965 // While copying the details of an exception could be expensive, in most runs,
1966 // this will be done at most once during the execution of the process, so the
1967 // total cost should be reasonable.
1969 ErrorDetails details;
1970 details.mType = *type;
1971 // If `exn` isn't an exception object, `ExtractErrorValues` could end up
1972 // calling `toString()`, which could in turn end up throwing an error. While
1973 // this should work, we want to avoid that complex use case. Fortunately, we
1974 // have already checked above that `exn` is an exception object, so nothing
1975 // such should happen.
1976 nsContentUtils::ExtractErrorValues(cx, exn, details.mFilename, &details.mLine,
1977 &details.mColumn, details.mMessage);
1979 JS::UniqueChars buf =
1980 JS::FormatStackDump(cx, /* showArgs = */ false, /* showLocals = */ false,
1981 /* showThisProps = */ false);
1982 CopyUTF8toUTF16(mozilla::MakeStringSpan(buf.get()), details.mStack);
1984 mThrownError.emplace(std::move(details));
1987 void CycleCollectedJSRuntime::ClearRecentDevError() {
1988 mErrorInterceptor.mThrownError.reset();
1991 bool CycleCollectedJSRuntime::GetRecentDevError(
1992 JSContext* cx, JS::MutableHandle<JS::Value> error) {
1993 if (!mErrorInterceptor.mThrownError) {
1994 return true;
1997 // Create a copy of the exception.
1998 JS::RootedObject obj(cx, JS_NewPlainObject(cx));
1999 if (!obj) {
2000 return false;
2003 JS::RootedValue message(cx);
2004 JS::RootedValue filename(cx);
2005 JS::RootedValue stack(cx);
2006 if (!ToJSValue(cx, mErrorInterceptor.mThrownError->mMessage, &message) ||
2007 !ToJSValue(cx, mErrorInterceptor.mThrownError->mFilename, &filename) ||
2008 !ToJSValue(cx, mErrorInterceptor.mThrownError->mStack, &stack)) {
2009 return false;
2012 // Build the object.
2013 const auto FLAGS = JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT;
2014 if (!JS_DefineProperty(cx, obj, "message", message, FLAGS) ||
2015 !JS_DefineProperty(cx, obj, "fileName", filename, FLAGS) ||
2016 !JS_DefineProperty(cx, obj, "lineNumber",
2017 mErrorInterceptor.mThrownError->mLine, FLAGS) ||
2018 !JS_DefineProperty(cx, obj, "stack", stack, FLAGS)) {
2019 return false;
2022 // Pass the result.
2023 error.setObject(*obj);
2024 return true;
2026 #endif // MOZ_JS_DEV_ERROR_INTERCEPTOR
2028 #undef MOZ_JS_DEV_ERROR_INTERCEPTOR