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