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/. */
6 
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.
12 //
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.
17 //
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.
22 //
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.
25 //
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.
29 //
30 // Phases:
31 //
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
38 //
39 // At some later point, when the cycle collector runs:
40 //
41 // 5. walk gray objects and add them to the cycle collector, cycle collect
42 //
43 // JS objects that are part of cycles the cycle collector breaks will be
44 // collected by the next JS GC.
45 //
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.
55 
56 #include "mozilla/CycleCollectedJSRuntime.h"
57 
58 #include <algorithm>
59 #include <utility>
60 
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/AutoEntryScript.h"
81 #include "mozilla/dom/DOMJSClass.h"
82 #include "mozilla/dom/JSExecutionManager.h"
83 #include "mozilla/dom/ProfileTimelineMarkerBinding.h"
84 #include "mozilla/dom/Promise.h"
85 #include "mozilla/dom/PromiseBinding.h"
86 #include "mozilla/dom/PromiseDebugging.h"
87 #include "mozilla/dom/ScriptSettings.h"
88 #include "nsContentUtils.h"
89 #include "nsCycleCollectionNoteRootCallback.h"
90 #include "nsCycleCollectionParticipant.h"
91 #include "nsCycleCollector.h"
92 #include "nsDOMJSUtils.h"
93 #include "nsExceptionHandler.h"
94 #include "nsJSUtils.h"
95 #include "nsStringBuffer.h"
96 #include "nsWrapperCache.h"
97 
98 #if defined(XP_MACOSX)
99 #  include "nsMacUtilsImpl.h"
100 #endif
101 
102 #include "nsThread.h"
103 #include "nsThreadUtils.h"
104 #include "xpcpublic.h"
105 
106 #ifdef NIGHTLY_BUILD
107 // For performance reasons, we make the JS Dev Error Interceptor a Nightly-only
108 // feature.
109 #  define MOZ_JS_DEV_ERROR_INTERCEPTOR = 1
110 #endif  // NIGHTLY_BUILD
111 
112 using namespace mozilla;
113 using namespace mozilla::dom;
114 
115 namespace mozilla {
116 
117 struct DeferredFinalizeFunctionHolder {
118   DeferredFinalizeFunction run;
119   void* data;
120 };
121 
122 class IncrementalFinalizeRunnable : public DiscardableRunnable {
123   typedef AutoTArray<DeferredFinalizeFunctionHolder, 16> DeferredFinalizeArray;
124   typedef CycleCollectedJSRuntime::DeferredFinalizerTable
125       DeferredFinalizerTable;
126 
127   CycleCollectedJSRuntime* mRuntime;
128   DeferredFinalizeArray mDeferredFinalizeFunctions;
129   uint32_t mFinalizeFunctionToRun;
130   bool mReleasing;
131 
132   static const PRTime SliceMillis = 5; /* ms */
133 
134  public:
135   IncrementalFinalizeRunnable(CycleCollectedJSRuntime* aRt,
136                               DeferredFinalizerTable& aFinalizerTable);
137   virtual ~IncrementalFinalizeRunnable();
138 
139   void ReleaseNow(bool aLimited);
140 
141   NS_DECL_NSIRUNNABLE
142 };
143 
144 }  // namespace mozilla
145 
146 struct NoteWeakMapChildrenTracer : public JS::CallbackTracer {
NoteWeakMapChildrenTracerNoteWeakMapChildrenTracer147   NoteWeakMapChildrenTracer(JSRuntime* aRt,
148                             nsCycleCollectionNoteRootCallback& aCb)
149       : JS::CallbackTracer(aRt, JS::TracerKind::Callback,
150                            JS::IdTraceAction::CanSkip),
151         mCb(aCb),
152         mTracedAny(false),
153         mMap(nullptr),
154         mKey(nullptr),
155         mKeyDelegate(nullptr) {}
156   void onChild(const JS::GCCellPtr& aThing) override;
157   nsCycleCollectionNoteRootCallback& mCb;
158   bool mTracedAny;
159   JSObject* mMap;
160   JS::GCCellPtr mKey;
161   JSObject* mKeyDelegate;
162 };
163 
onChild(const JS::GCCellPtr & aThing)164 void NoteWeakMapChildrenTracer::onChild(const JS::GCCellPtr& aThing) {
165   if (aThing.is<JSString>()) {
166     return;
167   }
168 
169   if (!JS::GCThingIsMarkedGray(aThing) && !mCb.WantAllTraces()) {
170     return;
171   }
172 
173   if (JS::IsCCTraceKind(aThing.kind())) {
174     mCb.NoteWeakMapping(mMap, mKey, mKeyDelegate, aThing);
175     mTracedAny = true;
176   } else {
177     JS::TraceChildren(this, aThing);
178   }
179 }
180 
181 struct NoteWeakMapsTracer : public js::WeakMapTracer {
NoteWeakMapsTracerNoteWeakMapsTracer182   NoteWeakMapsTracer(JSRuntime* aRt, nsCycleCollectionNoteRootCallback& aCccb)
183       : js::WeakMapTracer(aRt), mCb(aCccb), mChildTracer(aRt, aCccb) {}
184   void trace(JSObject* aMap, JS::GCCellPtr aKey, JS::GCCellPtr aValue) override;
185   nsCycleCollectionNoteRootCallback& mCb;
186   NoteWeakMapChildrenTracer mChildTracer;
187 };
188 
trace(JSObject * aMap,JS::GCCellPtr aKey,JS::GCCellPtr aValue)189 void NoteWeakMapsTracer::trace(JSObject* aMap, JS::GCCellPtr aKey,
190                                JS::GCCellPtr aValue) {
191   // If nothing that could be held alive by this entry is marked gray, return.
192   if ((!aKey || !JS::GCThingIsMarkedGray(aKey)) &&
193       MOZ_LIKELY(!mCb.WantAllTraces())) {
194     if (!aValue || !JS::GCThingIsMarkedGray(aValue) || aValue.is<JSString>()) {
195       return;
196     }
197   }
198 
199   // The cycle collector can only properly reason about weak maps if it can
200   // reason about the liveness of their keys, which in turn requires that
201   // the key can be represented in the cycle collector graph.  All existing
202   // uses of weak maps use either objects or scripts as keys, which are okay.
203   MOZ_ASSERT(JS::IsCCTraceKind(aKey.kind()));
204 
205   // As an emergency fallback for non-debug builds, if the key is not
206   // representable in the cycle collector graph, we treat it as marked.  This
207   // can cause leaks, but is preferable to ignoring the binding, which could
208   // cause the cycle collector to free live objects.
209   if (!JS::IsCCTraceKind(aKey.kind())) {
210     aKey = nullptr;
211   }
212 
213   JSObject* kdelegate = nullptr;
214   if (aKey.is<JSObject>()) {
215     kdelegate = js::UncheckedUnwrapWithoutExpose(&aKey.as<JSObject>());
216   }
217 
218   if (JS::IsCCTraceKind(aValue.kind())) {
219     mCb.NoteWeakMapping(aMap, aKey, kdelegate, aValue);
220   } else {
221     mChildTracer.mTracedAny = false;
222     mChildTracer.mMap = aMap;
223     mChildTracer.mKey = aKey;
224     mChildTracer.mKeyDelegate = kdelegate;
225 
226     if (!aValue.is<JSString>()) {
227       JS::TraceChildren(&mChildTracer, aValue);
228     }
229 
230     // The delegate could hold alive the key, so report something to the CC
231     // if we haven't already.
232     if (!mChildTracer.mTracedAny && aKey && JS::GCThingIsMarkedGray(aKey) &&
233         kdelegate) {
234       mCb.NoteWeakMapping(aMap, aKey, kdelegate, nullptr);
235     }
236   }
237 }
238 
239 // Report whether the key or value of a weak mapping entry are gray but need to
240 // be marked black.
ShouldWeakMappingEntryBeBlack(JSObject * aMap,JS::GCCellPtr aKey,JS::GCCellPtr aValue,bool * aKeyShouldBeBlack,bool * aValueShouldBeBlack)241 static void ShouldWeakMappingEntryBeBlack(JSObject* aMap, JS::GCCellPtr aKey,
242                                           JS::GCCellPtr aValue,
243                                           bool* aKeyShouldBeBlack,
244                                           bool* aValueShouldBeBlack) {
245   *aKeyShouldBeBlack = false;
246   *aValueShouldBeBlack = false;
247 
248   // If nothing that could be held alive by this entry is marked gray, return.
249   bool keyMightNeedMarking = aKey && JS::GCThingIsMarkedGray(aKey);
250   bool valueMightNeedMarking = aValue && JS::GCThingIsMarkedGray(aValue) &&
251                                aValue.kind() != JS::TraceKind::String;
252   if (!keyMightNeedMarking && !valueMightNeedMarking) {
253     return;
254   }
255 
256   if (!JS::IsCCTraceKind(aKey.kind())) {
257     aKey = nullptr;
258   }
259 
260   if (keyMightNeedMarking && aKey.is<JSObject>()) {
261     JSObject* kdelegate =
262         js::UncheckedUnwrapWithoutExpose(&aKey.as<JSObject>());
263     if (kdelegate && !JS::ObjectIsMarkedGray(kdelegate) &&
264         (!aMap || !JS::ObjectIsMarkedGray(aMap))) {
265       *aKeyShouldBeBlack = true;
266     }
267   }
268 
269   if (aValue && JS::GCThingIsMarkedGray(aValue) &&
270       (!aKey || !JS::GCThingIsMarkedGray(aKey)) &&
271       (!aMap || !JS::ObjectIsMarkedGray(aMap)) &&
272       aValue.kind() != JS::TraceKind::Shape) {
273     *aValueShouldBeBlack = true;
274   }
275 }
276 
277 struct FixWeakMappingGrayBitsTracer : public js::WeakMapTracer {
FixWeakMappingGrayBitsTracerFixWeakMappingGrayBitsTracer278   explicit FixWeakMappingGrayBitsTracer(JSRuntime* aRt)
279       : js::WeakMapTracer(aRt) {}
280 
FixAllFixWeakMappingGrayBitsTracer281   void FixAll() {
282     do {
283       mAnyMarked = false;
284       js::TraceWeakMaps(this);
285     } while (mAnyMarked);
286   }
287 
traceFixWeakMappingGrayBitsTracer288   void trace(JSObject* aMap, JS::GCCellPtr aKey,
289              JS::GCCellPtr aValue) override {
290     bool keyShouldBeBlack;
291     bool valueShouldBeBlack;
292     ShouldWeakMappingEntryBeBlack(aMap, aKey, aValue, &keyShouldBeBlack,
293                                   &valueShouldBeBlack);
294     if (keyShouldBeBlack && JS::UnmarkGrayGCThingRecursively(aKey)) {
295       mAnyMarked = true;
296     }
297 
298     if (valueShouldBeBlack && JS::UnmarkGrayGCThingRecursively(aValue)) {
299       mAnyMarked = true;
300     }
301   }
302 
303   MOZ_INIT_OUTSIDE_CTOR bool mAnyMarked;
304 };
305 
306 #ifdef DEBUG
307 // Check whether weak maps are marked correctly according to the logic above.
308 struct CheckWeakMappingGrayBitsTracer : public js::WeakMapTracer {
CheckWeakMappingGrayBitsTracerCheckWeakMappingGrayBitsTracer309   explicit CheckWeakMappingGrayBitsTracer(JSRuntime* aRt)
310       : js::WeakMapTracer(aRt), mFailed(false) {}
311 
CheckCheckWeakMappingGrayBitsTracer312   static bool Check(JSRuntime* aRt) {
313     CheckWeakMappingGrayBitsTracer tracer(aRt);
314     js::TraceWeakMaps(&tracer);
315     return !tracer.mFailed;
316   }
317 
traceCheckWeakMappingGrayBitsTracer318   void trace(JSObject* aMap, JS::GCCellPtr aKey,
319              JS::GCCellPtr aValue) override {
320     bool keyShouldBeBlack;
321     bool valueShouldBeBlack;
322     ShouldWeakMappingEntryBeBlack(aMap, aKey, aValue, &keyShouldBeBlack,
323                                   &valueShouldBeBlack);
324 
325     if (keyShouldBeBlack) {
326       fprintf(stderr, "Weak mapping key %p of map %p should be black\n",
327               aKey.asCell(), aMap);
328       mFailed = true;
329     }
330 
331     if (valueShouldBeBlack) {
332       fprintf(stderr, "Weak mapping value %p of map %p should be black\n",
333               aValue.asCell(), aMap);
334       mFailed = true;
335     }
336   }
337 
338   bool mFailed;
339 };
340 #endif  // DEBUG
341 
CheckParticipatesInCycleCollection(JS::GCCellPtr aThing,const char * aName,void * aClosure)342 static void CheckParticipatesInCycleCollection(JS::GCCellPtr aThing,
343                                                const char* aName,
344                                                void* aClosure) {
345   bool* cycleCollectionEnabled = static_cast<bool*>(aClosure);
346 
347   if (*cycleCollectionEnabled) {
348     return;
349   }
350 
351   if (JS::IsCCTraceKind(aThing.kind()) && JS::GCThingIsMarkedGray(aThing)) {
352     *cycleCollectionEnabled = true;
353   }
354 }
355 
356 NS_IMETHODIMP
TraverseNative(void * aPtr,nsCycleCollectionTraversalCallback & aCb)357 JSGCThingParticipant::TraverseNative(void* aPtr,
358                                      nsCycleCollectionTraversalCallback& aCb) {
359   auto runtime = reinterpret_cast<CycleCollectedJSRuntime*>(
360       reinterpret_cast<char*>(this) -
361       offsetof(CycleCollectedJSRuntime, mGCThingCycleCollectorGlobal));
362 
363   JS::GCCellPtr cellPtr(aPtr, JS::GCThingTraceKind(aPtr));
364   runtime->TraverseGCThing(CycleCollectedJSRuntime::TRAVERSE_FULL, cellPtr,
365                            aCb);
366   return NS_OK;
367 }
368 
369 // NB: This is only used to initialize the participant in
370 // CycleCollectedJSRuntime. It should never be used directly.
371 static JSGCThingParticipant sGCThingCycleCollectorGlobal;
372 
373 NS_IMETHODIMP
TraverseNative(void * aPtr,nsCycleCollectionTraversalCallback & aCb)374 JSZoneParticipant::TraverseNative(void* aPtr,
375                                   nsCycleCollectionTraversalCallback& aCb) {
376   auto runtime = reinterpret_cast<CycleCollectedJSRuntime*>(
377       reinterpret_cast<char*>(this) -
378       offsetof(CycleCollectedJSRuntime, mJSZoneCycleCollectorGlobal));
379 
380   MOZ_ASSERT(!aCb.WantAllTraces());
381   JS::Zone* zone = static_cast<JS::Zone*>(aPtr);
382 
383   runtime->TraverseZone(zone, aCb);
384   return NS_OK;
385 }
386 
387 struct TraversalTracer : public JS::CallbackTracer {
TraversalTracerTraversalTracer388   TraversalTracer(JSRuntime* aRt, nsCycleCollectionTraversalCallback& aCb)
389       : JS::CallbackTracer(aRt, JS::TracerKind::Callback,
390                            JS::TraceOptions(JS::WeakMapTraceAction::Skip,
391                                             JS::WeakEdgeTraceAction::Trace,
392                                             JS::IdTraceAction::CanSkip)),
393         mCb(aCb) {}
394   void onChild(const JS::GCCellPtr& aThing) override;
395   nsCycleCollectionTraversalCallback& mCb;
396 };
397 
onChild(const JS::GCCellPtr & aThing)398 void TraversalTracer::onChild(const JS::GCCellPtr& aThing) {
399   // Checking strings and symbols for being gray is rather slow, and we don't
400   // need either of them for the cycle collector.
401   if (aThing.is<JSString>() || aThing.is<JS::Symbol>()) {
402     return;
403   }
404 
405   // Don't traverse non-gray objects, unless we want all traces.
406   if (!JS::GCThingIsMarkedGray(aThing) && !mCb.WantAllTraces()) {
407     return;
408   }
409 
410   /*
411    * This function needs to be careful to avoid stack overflow. Normally, when
412    * IsCCTraceKind is true, the recursion terminates immediately as we just add
413    * |thing| to the CC graph. So overflow is only possible when there are long
414    * or cyclic chains of non-IsCCTraceKind GC things. Places where this can
415    * occur use special APIs to handle such chains iteratively.
416    */
417   if (JS::IsCCTraceKind(aThing.kind())) {
418     if (MOZ_UNLIKELY(mCb.WantDebugInfo())) {
419       char buffer[200];
420       context().getEdgeName(buffer, sizeof(buffer));
421       mCb.NoteNextEdgeName(buffer);
422     }
423     mCb.NoteJSChild(aThing);
424     return;
425   }
426 
427   // Allow re-use of this tracer inside trace callback.
428   JS::AutoClearTracingContext actc(this);
429 
430   if (aThing.is<js::Shape>()) {
431     // The maximum depth of traversal when tracing a Shape is unbounded, due to
432     // the parent pointers on the shape.
433     JS_TraceShapeCycleCollectorChildren(this, aThing);
434   } else {
435     JS::TraceChildren(this, aThing);
436   }
437 }
438 
439 /*
440  * The cycle collection participant for a Zone is intended to produce the same
441  * results as if all of the gray GCthings in a zone were merged into a single
442  * node, except for self-edges. This avoids the overhead of representing all of
443  * the GCthings in the zone in the cycle collector graph, which should be much
444  * faster if many of the GCthings in the zone are gray.
445  *
446  * Zone merging should not always be used, because it is a conservative
447  * approximation of the true cycle collector graph that can incorrectly identify
448  * some garbage objects as being live. For instance, consider two cycles that
449  * pass through a zone, where one is garbage and the other is live. If we merge
450  * the entire zone, the cycle collector will think that both are alive.
451  *
452  * We don't have to worry about losing track of a garbage cycle, because any
453  * such garbage cycle incorrectly identified as live must contain at least one
454  * C++ to JS edge, and XPConnect will always add the C++ object to the CC graph.
455  * (This is in contrast to pure C++ garbage cycles, which must always be
456  * properly identified, because we clear the purple buffer during every CC,
457  * which may contain the last reference to a garbage cycle.)
458  */
459 
460 // NB: This is only used to initialize the participant in
461 // CycleCollectedJSRuntime. It should never be used directly.
462 static const JSZoneParticipant sJSZoneCycleCollectorGlobal;
463 
JSObjectsTenuredCb(JSContext * aContext,void * aData)464 static void JSObjectsTenuredCb(JSContext* aContext, void* aData) {
465   static_cast<CycleCollectedJSRuntime*>(aData)->JSObjectsTenured();
466 }
467 
MozCrashWarningReporter(JSContext *,JSErrorReport *)468 static void MozCrashWarningReporter(JSContext*, JSErrorReport*) {
469   MOZ_CRASH("Why is someone touching JSAPI without an AutoJSAPI?");
470 }
471 
Entry()472 JSHolderMap::Entry::Entry() : Entry(nullptr, nullptr, nullptr) {}
473 
Entry(void * aHolder,nsScriptObjectTracer * aTracer,JS::Zone * aZone)474 JSHolderMap::Entry::Entry(void* aHolder, nsScriptObjectTracer* aTracer,
475                           JS::Zone* aZone)
476     : mHolder(aHolder),
477       mTracer(aTracer)
478 #ifdef DEBUG
479       ,
480       mZone(aZone)
481 #endif
482 {
483 }
484 
JSHolderMap()485 JSHolderMap::JSHolderMap() : mJSHolderMap(256) {}
486 
487 template <typename F>
ForEach(F && f,WhichHolders aWhich)488 inline void JSHolderMap::ForEach(F&& f, WhichHolders aWhich) {
489   // Multi-zone JS holders must always be considered.
490   ForEach(mAnyZoneJSHolders, f, nullptr);
491 
492   for (auto i = mPerZoneJSHolders.modIter(); !i.done(); i.next()) {
493     if (aWhich == HoldersInCollectingZones &&
494         !JS::ZoneIsCollecting(i.get().key())) {
495       continue;
496     }
497 
498     EntryVector* holders = i.get().value().get();
499     ForEach(*holders, f, i.get().key());
500     if (holders->IsEmpty()) {
501       i.remove();
502     }
503   }
504 }
505 
506 template <typename F>
ForEach(EntryVector & aJSHolders,const F & f,JS::Zone * aZone)507 inline void JSHolderMap::ForEach(EntryVector& aJSHolders, const F& f,
508                                  JS::Zone* aZone) {
509   for (auto iter = aJSHolders.Iter(); !iter.Done(); iter.Next()) {
510     Entry* entry = &iter.Get();
511 
512     // If the entry has been cleared, remove it and shrink the vector.
513     if (!entry->mHolder && !RemoveEntry(aJSHolders, entry)) {
514       break;  // Removed the last entry.
515     }
516 
517     MOZ_ASSERT_IF(aZone, entry->mZone == aZone);
518     f(entry->mHolder, entry->mTracer, aZone);
519   }
520 }
521 
RemoveEntry(EntryVector & aJSHolders,Entry * aEntry)522 bool JSHolderMap::RemoveEntry(EntryVector& aJSHolders, Entry* aEntry) {
523   MOZ_ASSERT(aEntry);
524   MOZ_ASSERT(!aEntry->mHolder);
525 
526   // Remove all dead entries from the end of the vector.
527   while (!aJSHolders.GetLast().mHolder && &aJSHolders.GetLast() != aEntry) {
528     aJSHolders.PopLast();
529   }
530 
531   // Swap the element we want to remove with the last one and update the hash
532   // table.
533   Entry* lastEntry = &aJSHolders.GetLast();
534   if (aEntry != lastEntry) {
535     MOZ_ASSERT(lastEntry->mHolder);
536     *aEntry = *lastEntry;
537     MOZ_ASSERT(mJSHolderMap.has(aEntry->mHolder));
538     MOZ_ALWAYS_TRUE(mJSHolderMap.put(aEntry->mHolder, aEntry));
539   }
540 
541   aJSHolders.PopLast();
542 
543   // Return whether aEntry is still in the vector.
544   return aEntry != lastEntry;
545 }
546 
Has(void * aHolder) const547 inline bool JSHolderMap::Has(void* aHolder) const {
548   return mJSHolderMap.has(aHolder);
549 }
550 
Get(void * aHolder) const551 inline nsScriptObjectTracer* JSHolderMap::Get(void* aHolder) const {
552   auto ptr = mJSHolderMap.lookup(aHolder);
553   if (!ptr) {
554     return nullptr;
555   }
556 
557   Entry* entry = ptr->value();
558   MOZ_ASSERT(entry->mHolder == aHolder);
559   return entry->mTracer;
560 }
561 
Extract(void * aHolder)562 inline nsScriptObjectTracer* JSHolderMap::Extract(void* aHolder) {
563   MOZ_ASSERT(aHolder);
564 
565   auto ptr = mJSHolderMap.lookup(aHolder);
566   if (!ptr) {
567     return nullptr;
568   }
569 
570   Entry* entry = ptr->value();
571   MOZ_ASSERT(entry->mHolder == aHolder);
572   nsScriptObjectTracer* tracer = entry->mTracer;
573 
574   // Clear the entry's contents. It will be removed during the next iteration.
575   *entry = Entry();
576 
577   mJSHolderMap.remove(ptr);
578 
579   return tracer;
580 }
581 
Put(void * aHolder,nsScriptObjectTracer * aTracer,JS::Zone * aZone)582 inline void JSHolderMap::Put(void* aHolder, nsScriptObjectTracer* aTracer,
583                              JS::Zone* aZone) {
584   MOZ_ASSERT(aHolder);
585   MOZ_ASSERT(aTracer);
586 
587   // Don't associate multi-zone holders with a zone, even if one is supplied.
588   if (aTracer->IsMultiZoneJSHolder()) {
589     aZone = nullptr;
590   }
591 
592   auto ptr = mJSHolderMap.lookupForAdd(aHolder);
593   if (ptr) {
594     Entry* entry = ptr->value();
595 #ifdef DEBUG
596     MOZ_ASSERT(entry->mHolder == aHolder);
597     MOZ_ASSERT(entry->mTracer == aTracer,
598                "Don't call HoldJSObjects in superclass ctors");
599     if (aZone) {
600       if (entry->mZone) {
601         MOZ_ASSERT(entry->mZone == aZone);
602       } else {
603         entry->mZone = aZone;
604       }
605     }
606 #endif
607     entry->mTracer = aTracer;
608     return;
609   }
610 
611   EntryVector* vector = &mAnyZoneJSHolders;
612   if (aZone) {
613     auto ptr = mPerZoneJSHolders.lookupForAdd(aZone);
614     if (!ptr) {
615       MOZ_ALWAYS_TRUE(
616           mPerZoneJSHolders.add(ptr, aZone, MakeUnique<EntryVector>()));
617     }
618     vector = ptr->value().get();
619   }
620 
621   vector->InfallibleAppend(Entry{aHolder, aTracer, aZone});
622   MOZ_ALWAYS_TRUE(mJSHolderMap.add(ptr, aHolder, &vector->GetLast()));
623 }
624 
SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const625 size_t JSHolderMap::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
626   size_t n = 0;
627 
628   // We're deliberately not measuring anything hanging off the entries in
629   // mJSHolders.
630   n += mJSHolderMap.shallowSizeOfExcludingThis(aMallocSizeOf);
631   n += mAnyZoneJSHolders.SizeOfExcludingThis(aMallocSizeOf);
632   n += mPerZoneJSHolders.shallowSizeOfExcludingThis(aMallocSizeOf);
633   for (auto i = mPerZoneJSHolders.iter(); !i.done(); i.next()) {
634     n += i.get().value()->SizeOfExcludingThis(aMallocSizeOf);
635   }
636 
637   return n;
638 }
639 
CycleCollectedJSRuntime(JSContext * aCx)640 CycleCollectedJSRuntime::CycleCollectedJSRuntime(JSContext* aCx)
641     : mContext(nullptr),
642       mGCThingCycleCollectorGlobal(sGCThingCycleCollectorGlobal),
643       mJSZoneCycleCollectorGlobal(sJSZoneCycleCollectorGlobal),
644       mJSRuntime(JS_GetRuntime(aCx)),
645       mHasPendingIdleGCTask(false),
646       mPrevGCSliceCallback(nullptr),
647       mPrevGCNurseryCollectionCallback(nullptr),
648       mOutOfMemoryState(OOMState::OK),
649       mLargeAllocationFailureState(OOMState::OK)
650 #ifdef DEBUG
651       ,
652       mShutdownCalled(false)
653 #endif
654 {
655   MOZ_COUNT_CTOR(CycleCollectedJSRuntime);
656   MOZ_ASSERT(aCx);
657   MOZ_ASSERT(mJSRuntime);
658 
659 #if defined(XP_MACOSX)
660   if (!XRE_IsParentProcess()) {
661     nsMacUtilsImpl::EnableTCSMIfAvailable();
662   }
663 #endif
664 
665   if (!JS_AddExtraGCRootsTracer(aCx, TraceBlackJS, this)) {
666     MOZ_CRASH("JS_AddExtraGCRootsTracer failed");
667   }
668   JS_SetGrayGCRootsTracer(aCx, TraceGrayJS, this);
669   JS_SetGCCallback(aCx, GCCallback, this);
670   mPrevGCSliceCallback = JS::SetGCSliceCallback(aCx, GCSliceCallback);
671 
672   if (NS_IsMainThread()) {
673     // We would like to support all threads here, but the way timeline consumers
674     // are set up currently, you can either add a marker for one specific
675     // docshell, or for every consumer globally. We would like to add a marker
676     // for every consumer observing anything on this thread, but that is not
677     // currently possible. For now, add global markers only when we are on the
678     // main thread, since the UI for this tracing data only displays data
679     // relevant to the main-thread.
680     mPrevGCNurseryCollectionCallback =
681         JS::SetGCNurseryCollectionCallback(aCx, GCNurseryCollectionCallback);
682   }
683 
684   JS_SetObjectsTenuredCallback(aCx, JSObjectsTenuredCb, this);
685   JS::SetOutOfMemoryCallback(aCx, OutOfMemoryCallback, this);
686   JS::SetWaitCallback(mJSRuntime, BeforeWaitCallback, AfterWaitCallback,
687                       sizeof(dom::AutoYieldJSThreadExecution));
688   JS::SetWarningReporter(aCx, MozCrashWarningReporter);
689 
690   js::AutoEnterOOMUnsafeRegion::setAnnotateOOMAllocationSizeCallback(
691       CrashReporter::AnnotateOOMAllocationSize);
692 
693   static js::DOMCallbacks DOMcallbacks = {InstanceClassHasProtoAtDepth};
694   SetDOMCallbacks(aCx, &DOMcallbacks);
695   js::SetScriptEnvironmentPreparer(aCx, &mEnvironmentPreparer);
696 
697   JS::dbg::SetDebuggerMallocSizeOf(aCx, moz_malloc_size_of);
698 
699 #ifdef MOZ_JS_DEV_ERROR_INTERCEPTOR
700   JS_SetErrorInterceptorCallback(mJSRuntime, &mErrorInterceptor);
701 #endif  // MOZ_JS_DEV_ERROR_INTERCEPTOR
702 
703   JS_SetDestroyZoneCallback(aCx, OnZoneDestroyed);
704 }
705 
706 #ifdef NS_BUILD_REFCNT_LOGGING
707 class JSLeakTracer : public JS::CallbackTracer {
708  public:
JSLeakTracer(JSRuntime * aRuntime)709   explicit JSLeakTracer(JSRuntime* aRuntime)
710       : JS::CallbackTracer(aRuntime, JS::TracerKind::Callback,
711                            JS::WeakMapTraceAction::TraceKeysAndValues) {}
712 
713  private:
onChild(const JS::GCCellPtr & thing)714   void onChild(const JS::GCCellPtr& thing) override {
715     const char* kindName = JS::GCTraceKindToAscii(thing.kind());
716     size_t size = JS::GCTraceKindSize(thing.kind());
717     MOZ_LOG_CTOR(thing.asCell(), kindName, size);
718   }
719 };
720 #endif
721 
Shutdown(JSContext * cx)722 void CycleCollectedJSRuntime::Shutdown(JSContext* cx) {
723 #ifdef MOZ_JS_DEV_ERROR_INTERCEPTOR
724   mErrorInterceptor.Shutdown(mJSRuntime);
725 #endif  // MOZ_JS_DEV_ERROR_INTERCEPTOR
726 
727   // There should not be any roots left to trace at this point. Ensure any that
728   // remain are flagged as leaks.
729 #ifdef NS_BUILD_REFCNT_LOGGING
730   JSLeakTracer tracer(Runtime());
731   TraceNativeBlackRoots(&tracer);
732   TraceNativeGrayRoots(&tracer, JSHolderMap::AllHolders);
733 #endif
734 
735 #ifdef DEBUG
736   mShutdownCalled = true;
737 #endif
738 
739   JS_SetDestroyZoneCallback(cx, nullptr);
740 }
741 
~CycleCollectedJSRuntime()742 CycleCollectedJSRuntime::~CycleCollectedJSRuntime() {
743   MOZ_COUNT_DTOR(CycleCollectedJSRuntime);
744   MOZ_ASSERT(!mDeferredFinalizerTable.Count());
745   MOZ_ASSERT(!mFinalizeRunnable);
746   MOZ_ASSERT(mShutdownCalled);
747 }
748 
SetContext(CycleCollectedJSContext * aContext)749 void CycleCollectedJSRuntime::SetContext(CycleCollectedJSContext* aContext) {
750   MOZ_ASSERT(!mContext || !aContext, "Don't replace the context!");
751   mContext = aContext;
752 }
753 
SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const754 size_t CycleCollectedJSRuntime::SizeOfExcludingThis(
755     MallocSizeOf aMallocSizeOf) const {
756   return mJSHolders.SizeOfExcludingThis(aMallocSizeOf);
757 }
758 
UnmarkSkippableJSHolders()759 void CycleCollectedJSRuntime::UnmarkSkippableJSHolders() {
760   mJSHolders.ForEach([](void* holder, nsScriptObjectTracer* tracer,
761                         JS::Zone* zone) { tracer->CanSkip(holder, true); });
762 }
763 
DescribeGCThing(bool aIsMarked,JS::GCCellPtr aThing,nsCycleCollectionTraversalCallback & aCb) const764 void CycleCollectedJSRuntime::DescribeGCThing(
765     bool aIsMarked, JS::GCCellPtr aThing,
766     nsCycleCollectionTraversalCallback& aCb) const {
767   if (!aCb.WantDebugInfo()) {
768     aCb.DescribeGCedNode(aIsMarked, "JS Object");
769     return;
770   }
771 
772   char name[72];
773   uint64_t compartmentAddress = 0;
774   if (aThing.is<JSObject>()) {
775     JSObject* obj = &aThing.as<JSObject>();
776     compartmentAddress = (uint64_t)JS::GetCompartment(obj);
777     const JSClass* clasp = JS::GetClass(obj);
778 
779     // Give the subclass a chance to do something
780     if (DescribeCustomObjects(obj, clasp, name)) {
781       // Nothing else to do!
782     } else if (js::IsFunctionObject(obj)) {
783       JSFunction* fun = JS_GetObjectFunction(obj);
784       JSString* str = JS_GetFunctionDisplayId(fun);
785       if (str) {
786         JSLinearString* linear = JS_ASSERT_STRING_IS_LINEAR(str);
787         nsAutoString chars;
788         AssignJSLinearString(chars, linear);
789         NS_ConvertUTF16toUTF8 fname(chars);
790         SprintfLiteral(name, "JS Object (Function - %s)", fname.get());
791       } else {
792         SprintfLiteral(name, "JS Object (Function)");
793       }
794     } else {
795       SprintfLiteral(name, "JS Object (%s)", clasp->name);
796     }
797   } else {
798     SprintfLiteral(name, "%s", JS::GCTraceKindToAscii(aThing.kind()));
799   }
800 
801   // Disable printing global for objects while we figure out ObjShrink fallout.
802   aCb.DescribeGCedNode(aIsMarked, name, compartmentAddress);
803 }
804 
NoteGCThingJSChildren(JS::GCCellPtr aThing,nsCycleCollectionTraversalCallback & aCb) const805 void CycleCollectedJSRuntime::NoteGCThingJSChildren(
806     JS::GCCellPtr aThing, nsCycleCollectionTraversalCallback& aCb) const {
807   TraversalTracer trc(mJSRuntime, aCb);
808   JS::TraceChildren(&trc, aThing);
809 }
810 
NoteGCThingXPCOMChildren(const JSClass * aClasp,JSObject * aObj,nsCycleCollectionTraversalCallback & aCb) const811 void CycleCollectedJSRuntime::NoteGCThingXPCOMChildren(
812     const JSClass* aClasp, JSObject* aObj,
813     nsCycleCollectionTraversalCallback& aCb) const {
814   MOZ_ASSERT(aClasp);
815   MOZ_ASSERT(aClasp == JS::GetClass(aObj));
816 
817   JS::Rooted<JSObject*> obj(RootingCx(), aObj);
818 
819   if (NoteCustomGCThingXPCOMChildren(aClasp, obj, aCb)) {
820     // Nothing else to do!
821     return;
822   }
823 
824   // XXX This test does seem fragile, we should probably whitelist classes
825   //     that do hold a strong reference, but that might not be possible.
826   if (aClasp->flags & JSCLASS_HAS_PRIVATE &&
827       aClasp->flags & JSCLASS_PRIVATE_IS_NSISUPPORTS) {
828     NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "JS::GetPrivate(obj)");
829     aCb.NoteXPCOMChild(static_cast<nsISupports*>(JS::GetPrivate(obj)));
830     return;
831   }
832 
833   const DOMJSClass* domClass = GetDOMClass(aClasp);
834   if (domClass) {
835     NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "UnwrapDOMObject(obj)");
836     // It's possible that our object is an unforgeable holder object, in
837     // which case it doesn't actually have a C++ DOM object associated with
838     // it.  Use UnwrapPossiblyNotInitializedDOMObject, which produces null in
839     // that case, since NoteXPCOMChild/NoteNativeChild are null-safe.
840     if (domClass->mDOMObjectIsISupports) {
841       aCb.NoteXPCOMChild(
842           UnwrapPossiblyNotInitializedDOMObject<nsISupports>(obj));
843     } else if (domClass->mParticipant) {
844       aCb.NoteNativeChild(UnwrapPossiblyNotInitializedDOMObject<void>(obj),
845                           domClass->mParticipant);
846     }
847     return;
848   }
849 
850   if (IsRemoteObjectProxy(obj)) {
851     auto handler =
852         static_cast<const RemoteObjectProxyBase*>(js::GetProxyHandler(obj));
853     return handler->NoteChildren(obj, aCb);
854   }
855 
856   JS::Value value = js::MaybeGetScriptPrivate(obj);
857   if (!value.isUndefined()) {
858     aCb.NoteXPCOMChild(static_cast<nsISupports*>(value.toPrivate()));
859   }
860 }
861 
TraverseGCThing(TraverseSelect aTs,JS::GCCellPtr aThing,nsCycleCollectionTraversalCallback & aCb)862 void CycleCollectedJSRuntime::TraverseGCThing(
863     TraverseSelect aTs, JS::GCCellPtr aThing,
864     nsCycleCollectionTraversalCallback& aCb) {
865   bool isMarkedGray = JS::GCThingIsMarkedGray(aThing);
866 
867   if (aTs == TRAVERSE_FULL) {
868     DescribeGCThing(!isMarkedGray, aThing, aCb);
869   }
870 
871   // If this object is alive, then all of its children are alive. For JS
872   // objects, the black-gray invariant ensures the children are also marked
873   // black. For C++ objects, the ref count from this object will keep them
874   // alive. Thus we don't need to trace our children, unless we are debugging
875   // using WantAllTraces.
876   if (!isMarkedGray && !aCb.WantAllTraces()) {
877     return;
878   }
879 
880   if (aTs == TRAVERSE_FULL) {
881     NoteGCThingJSChildren(aThing, aCb);
882   }
883 
884   if (aThing.is<JSObject>()) {
885     JSObject* obj = &aThing.as<JSObject>();
886     NoteGCThingXPCOMChildren(JS::GetClass(obj), obj, aCb);
887   }
888 }
889 
890 struct TraverseObjectShimClosure {
891   nsCycleCollectionTraversalCallback& cb;
892   CycleCollectedJSRuntime* self;
893 };
894 
TraverseZone(JS::Zone * aZone,nsCycleCollectionTraversalCallback & aCb)895 void CycleCollectedJSRuntime::TraverseZone(
896     JS::Zone* aZone, nsCycleCollectionTraversalCallback& aCb) {
897   /*
898    * We treat the zone as being gray. We handle non-gray GCthings in the
899    * zone by not reporting their children to the CC. The black-gray invariant
900    * ensures that any JS children will also be non-gray, and thus don't need to
901    * be added to the graph. For C++ children, not representing the edge from the
902    * non-gray JS GCthings to the C++ object will keep the child alive.
903    *
904    * We don't allow zone merging in a WantAllTraces CC, because then these
905    * assumptions don't hold.
906    */
907   aCb.DescribeGCedNode(false, "JS Zone");
908 
909   /*
910    * Every JS child of everything in the zone is either in the zone
911    * or is a cross-compartment wrapper. In the former case, we don't need to
912    * represent these edges in the CC graph because JS objects are not ref
913    * counted. In the latter case, the JS engine keeps a map of these wrappers,
914    * which we iterate over. Edges between compartments in the same zone will add
915    * unnecessary loop edges to the graph (bug 842137).
916    */
917   TraversalTracer trc(mJSRuntime, aCb);
918   js::TraceGrayWrapperTargets(&trc, aZone);
919 
920   /*
921    * To find C++ children of things in the zone, we scan every JS Object in
922    * the zone. Only JS Objects can have C++ children.
923    */
924   TraverseObjectShimClosure closure = {aCb, this};
925   js::IterateGrayObjects(aZone, TraverseObjectShim, &closure);
926 }
927 
928 /* static */
TraverseObjectShim(void * aData,JS::GCCellPtr aThing,const JS::AutoRequireNoGC & nogc)929 void CycleCollectedJSRuntime::TraverseObjectShim(
930     void* aData, JS::GCCellPtr aThing, const JS::AutoRequireNoGC& nogc) {
931   TraverseObjectShimClosure* closure =
932       static_cast<TraverseObjectShimClosure*>(aData);
933 
934   MOZ_ASSERT(aThing.is<JSObject>());
935   closure->self->TraverseGCThing(CycleCollectedJSRuntime::TRAVERSE_CPP, aThing,
936                                  closure->cb);
937 }
938 
TraverseNativeRoots(nsCycleCollectionNoteRootCallback & aCb)939 void CycleCollectedJSRuntime::TraverseNativeRoots(
940     nsCycleCollectionNoteRootCallback& aCb) {
941   // NB: This is here just to preserve the existing XPConnect order. I doubt it
942   // would hurt to do this after the JS holders.
943   TraverseAdditionalNativeRoots(aCb);
944 
945   mJSHolders.ForEach(
946       [&aCb](void* holder, nsScriptObjectTracer* tracer, JS::Zone* zone) {
947         bool noteRoot = false;
948         if (MOZ_UNLIKELY(aCb.WantAllTraces())) {
949           noteRoot = true;
950         } else {
951           tracer->Trace(holder,
952                         TraceCallbackFunc(CheckParticipatesInCycleCollection),
953                         &noteRoot);
954         }
955 
956         if (noteRoot) {
957           aCb.NoteNativeRoot(holder, tracer);
958         }
959       });
960 }
961 
962 /* static */
TraceBlackJS(JSTracer * aTracer,void * aData)963 void CycleCollectedJSRuntime::TraceBlackJS(JSTracer* aTracer, void* aData) {
964   CycleCollectedJSRuntime* self = static_cast<CycleCollectedJSRuntime*>(aData);
965 
966   self->TraceNativeBlackRoots(aTracer);
967 }
968 
969 /* static */
TraceGrayJS(JSTracer * aTracer,void * aData)970 void CycleCollectedJSRuntime::TraceGrayJS(JSTracer* aTracer, void* aData) {
971   CycleCollectedJSRuntime* self = static_cast<CycleCollectedJSRuntime*>(aData);
972 
973   // Mark these roots as gray so the CC can walk them later.
974 
975   JSHolderMap::WhichHolders which = JSHolderMap::HoldersInCollectingZones;
976   if (JS::AtomsZoneIsCollecting(self->Runtime())) {
977     // Any holder may point into the atoms zone.
978     which = JSHolderMap::AllHolders;
979   }
980 
981   self->TraceNativeGrayRoots(aTracer, which);
982 }
983 
984 /* static */
GCCallback(JSContext * aContext,JSGCStatus aStatus,JS::GCReason aReason,void * aData)985 void CycleCollectedJSRuntime::GCCallback(JSContext* aContext,
986                                          JSGCStatus aStatus,
987                                          JS::GCReason aReason, void* aData) {
988   CycleCollectedJSRuntime* self = static_cast<CycleCollectedJSRuntime*>(aData);
989 
990   MOZ_ASSERT(CycleCollectedJSContext::Get()->Context() == aContext);
991   MOZ_ASSERT(CycleCollectedJSContext::Get()->Runtime() == self);
992 
993   self->OnGC(aContext, aStatus, aReason);
994 }
995 
996 /* static */
GCSliceCallback(JSContext * aContext,JS::GCProgress aProgress,const JS::GCDescription & aDesc)997 void CycleCollectedJSRuntime::GCSliceCallback(JSContext* aContext,
998                                               JS::GCProgress aProgress,
999                                               const JS::GCDescription& aDesc) {
1000   CycleCollectedJSRuntime* self = CycleCollectedJSRuntime::Get();
1001   MOZ_ASSERT(CycleCollectedJSContext::Get()->Context() == aContext);
1002 
1003   if (profiler_thread_is_being_profiled()) {
1004     if (aProgress == JS::GC_CYCLE_END) {
1005       struct GCMajorMarker {
1006         static constexpr mozilla::Span<const char> MarkerTypeName() {
1007           return mozilla::MakeStringSpan("GCMajor");
1008         }
1009         static void StreamJSONMarkerData(
1010             mozilla::baseprofiler::SpliceableJSONWriter& aWriter,
1011             const mozilla::ProfilerString8View& aTimingJSON) {
1012           if (aTimingJSON.Length() != 0) {
1013             aWriter.SplicedJSONProperty("timings", aTimingJSON);
1014           } else {
1015             aWriter.NullProperty("timings");
1016           }
1017         }
1018         static mozilla::MarkerSchema MarkerTypeDisplay() {
1019           using MS = mozilla::MarkerSchema;
1020           MS schema{MS::Location::markerChart, MS::Location::markerTable,
1021                     MS::Location::timelineMemory};
1022           schema.AddStaticLabelValue(
1023               "Description",
1024               "Summary data for an entire major GC, encompassing a set of "
1025               "incremental slices. The main thread is not blocked for the "
1026               "entire major GC interval, only for the individual slices.");
1027           // No display instructions here, there is special handling in the
1028           // front-end.
1029           return schema;
1030         }
1031       };
1032 
1033       profiler_add_marker("GCMajor", baseprofiler::category::GCCC,
1034                           MarkerTiming::Interval(aDesc.startTime(aContext),
1035                                                  aDesc.endTime(aContext)),
1036                           GCMajorMarker{},
1037                           ProfilerString8View::WrapNullTerminatedString(
1038                               aDesc.formatJSONProfiler(aContext).get()));
1039     } else if (aProgress == JS::GC_SLICE_END) {
1040       struct GCSliceMarker {
1041         static constexpr mozilla::Span<const char> MarkerTypeName() {
1042           return mozilla::MakeStringSpan("GCSlice");
1043         }
1044         static void StreamJSONMarkerData(
1045             mozilla::baseprofiler::SpliceableJSONWriter& aWriter,
1046             const mozilla::ProfilerString8View& aTimingJSON) {
1047           if (aTimingJSON.Length() != 0) {
1048             aWriter.SplicedJSONProperty("timings", aTimingJSON);
1049           } else {
1050             aWriter.NullProperty("timings");
1051           }
1052         }
1053         static mozilla::MarkerSchema MarkerTypeDisplay() {
1054           using MS = mozilla::MarkerSchema;
1055           MS schema{MS::Location::markerChart, MS::Location::markerTable,
1056                     MS::Location::timelineMemory};
1057           schema.AddStaticLabelValue(
1058               "Description",
1059               "One slice of an incremental garbage collection (GC). The main "
1060               "thread is blocked during this time.");
1061           // No display instructions here, there is special handling in the
1062           // front-end.
1063           return schema;
1064         }
1065       };
1066 
1067       profiler_add_marker("GCSlice", baseprofiler::category::GCCC,
1068                           MarkerTiming::Interval(aDesc.lastSliceStart(aContext),
1069                                                  aDesc.lastSliceEnd(aContext)),
1070                           GCSliceMarker{},
1071                           ProfilerString8View::WrapNullTerminatedString(
1072                               aDesc.sliceToJSONProfiler(aContext).get()));
1073     }
1074   }
1075 
1076   if (aProgress == JS::GC_CYCLE_END &&
1077       JS::dbg::FireOnGarbageCollectionHookRequired(aContext)) {
1078     JS::GCReason reason = aDesc.reason_;
1079     Unused << NS_WARN_IF(
1080         NS_FAILED(DebuggerOnGCRunnable::Enqueue(aContext, aDesc)) &&
1081         reason != JS::GCReason::SHUTDOWN_CC &&
1082         reason != JS::GCReason::DESTROY_RUNTIME &&
1083         reason != JS::GCReason::XPCONNECT_SHUTDOWN);
1084   }
1085 
1086   if (self->mPrevGCSliceCallback) {
1087     self->mPrevGCSliceCallback(aContext, aProgress, aDesc);
1088   }
1089 }
1090 
1091 class MinorGCMarker : public TimelineMarker {
1092  private:
1093   JS::GCReason mReason;
1094 
1095  public:
MinorGCMarker(MarkerTracingType aTracingType,JS::GCReason aReason)1096   MinorGCMarker(MarkerTracingType aTracingType, JS::GCReason aReason)
1097       : TimelineMarker("MinorGC", aTracingType, MarkerStackRequest::NO_STACK),
1098         mReason(aReason) {
1099     MOZ_ASSERT(aTracingType == MarkerTracingType::START ||
1100                aTracingType == MarkerTracingType::END);
1101   }
1102 
MinorGCMarker(JS::GCNurseryProgress aProgress,JS::GCReason aReason)1103   MinorGCMarker(JS::GCNurseryProgress aProgress, JS::GCReason aReason)
1104       : TimelineMarker(
1105             "MinorGC",
1106             aProgress == JS::GCNurseryProgress::GC_NURSERY_COLLECTION_START
1107                 ? MarkerTracingType::START
1108                 : MarkerTracingType::END,
1109             MarkerStackRequest::NO_STACK),
1110         mReason(aReason) {}
1111 
AddDetails(JSContext * aCx,dom::ProfileTimelineMarker & aMarker)1112   virtual void AddDetails(JSContext* aCx,
1113                           dom::ProfileTimelineMarker& aMarker) override {
1114     TimelineMarker::AddDetails(aCx, aMarker);
1115 
1116     if (GetTracingType() == MarkerTracingType::START) {
1117       auto reason = JS::ExplainGCReason(mReason);
1118       aMarker.mCauseName.Construct(NS_ConvertUTF8toUTF16(reason));
1119     }
1120   }
1121 
Clone()1122   virtual UniquePtr<AbstractTimelineMarker> Clone() override {
1123     auto clone = MakeUnique<MinorGCMarker>(GetTracingType(), mReason);
1124     clone->SetCustomTime(GetTime());
1125     return UniquePtr<AbstractTimelineMarker>(std::move(clone));
1126   }
1127 };
1128 
1129 /* static */
GCNurseryCollectionCallback(JSContext * aContext,JS::GCNurseryProgress aProgress,JS::GCReason aReason)1130 void CycleCollectedJSRuntime::GCNurseryCollectionCallback(
1131     JSContext* aContext, JS::GCNurseryProgress aProgress,
1132     JS::GCReason aReason) {
1133   CycleCollectedJSRuntime* self = CycleCollectedJSRuntime::Get();
1134   MOZ_ASSERT(CycleCollectedJSContext::Get()->Context() == aContext);
1135   MOZ_ASSERT(NS_IsMainThread());
1136 
1137   RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
1138   if (timelines && !timelines->IsEmpty()) {
1139     UniquePtr<AbstractTimelineMarker> abstractMarker(
1140         MakeUnique<MinorGCMarker>(aProgress, aReason));
1141     timelines->AddMarkerForAllObservedDocShells(abstractMarker);
1142   }
1143 
1144   if (aProgress == JS::GCNurseryProgress::GC_NURSERY_COLLECTION_START) {
1145     self->mLatestNurseryCollectionStart = TimeStamp::Now();
1146   } else if (aProgress == JS::GCNurseryProgress::GC_NURSERY_COLLECTION_END &&
1147              profiler_thread_is_being_profiled()) {
1148     struct GCMinorMarker {
1149       static constexpr mozilla::Span<const char> MarkerTypeName() {
1150         return mozilla::MakeStringSpan("GCMinor");
1151       }
1152       static void StreamJSONMarkerData(
1153           mozilla::baseprofiler::SpliceableJSONWriter& aWriter,
1154           const mozilla::ProfilerString8View& aTimingJSON) {
1155         if (aTimingJSON.Length() != 0) {
1156           aWriter.SplicedJSONProperty("nursery", aTimingJSON);
1157         } else {
1158           aWriter.NullProperty("nursery");
1159         }
1160       }
1161       static mozilla::MarkerSchema MarkerTypeDisplay() {
1162         using MS = mozilla::MarkerSchema;
1163         MS schema{MS::Location::markerChart, MS::Location::markerTable,
1164                   MS::Location::timelineMemory};
1165         schema.AddStaticLabelValue(
1166             "Description",
1167             "A minor GC (aka nursery collection) to clear out the buffer used "
1168             "for recent allocations and move surviving data to the tenured "
1169             "(long-lived) heap.");
1170         // No display instructions here, there is special handling in the
1171         // front-end.
1172         return schema;
1173       }
1174     };
1175 
1176     profiler_add_marker(
1177         "GCMinor", baseprofiler::category::GCCC,
1178         MarkerTiming::IntervalUntilNowFrom(self->mLatestNurseryCollectionStart),
1179         GCMinorMarker{},
1180         ProfilerString8View::WrapNullTerminatedString(
1181             JS::MinorGcToJSON(aContext).get()));
1182   }
1183 
1184   if (self->mPrevGCNurseryCollectionCallback) {
1185     self->mPrevGCNurseryCollectionCallback(aContext, aProgress, aReason);
1186   }
1187 }
1188 
1189 /* static */
OutOfMemoryCallback(JSContext * aContext,void * aData)1190 void CycleCollectedJSRuntime::OutOfMemoryCallback(JSContext* aContext,
1191                                                   void* aData) {
1192   CycleCollectedJSRuntime* self = static_cast<CycleCollectedJSRuntime*>(aData);
1193 
1194   MOZ_ASSERT(CycleCollectedJSContext::Get()->Context() == aContext);
1195   MOZ_ASSERT(CycleCollectedJSContext::Get()->Runtime() == self);
1196 
1197   self->OnOutOfMemory();
1198 }
1199 
1200 /* static */
BeforeWaitCallback(uint8_t * aMemory)1201 void* CycleCollectedJSRuntime::BeforeWaitCallback(uint8_t* aMemory) {
1202   MOZ_ASSERT(aMemory);
1203 
1204   // aMemory is stack allocated memory to contain our RAII object. This allows
1205   // for us to avoid allocations on the heap during this callback.
1206   return new (aMemory) dom::AutoYieldJSThreadExecution;
1207 }
1208 
1209 /* static */
AfterWaitCallback(void * aCookie)1210 void CycleCollectedJSRuntime::AfterWaitCallback(void* aCookie) {
1211   MOZ_ASSERT(aCookie);
1212   static_cast<dom::AutoYieldJSThreadExecution*>(aCookie)
1213       ->~AutoYieldJSThreadExecution();
1214 }
1215 
1216 struct JsGcTracer : public TraceCallbacks {
TraceJsGcTracer1217   virtual void Trace(JS::Heap<JS::Value>* aPtr, const char* aName,
1218                      void* aClosure) const override {
1219     JS::TraceEdge(static_cast<JSTracer*>(aClosure), aPtr, aName);
1220   }
TraceJsGcTracer1221   virtual void Trace(JS::Heap<jsid>* aPtr, const char* aName,
1222                      void* aClosure) const override {
1223     JS::TraceEdge(static_cast<JSTracer*>(aClosure), aPtr, aName);
1224   }
TraceJsGcTracer1225   virtual void Trace(JS::Heap<JSObject*>* aPtr, const char* aName,
1226                      void* aClosure) const override {
1227     JS::TraceEdge(static_cast<JSTracer*>(aClosure), aPtr, aName);
1228   }
TraceJsGcTracer1229   virtual void Trace(nsWrapperCache* aPtr, const char* aName,
1230                      void* aClosure) const override {
1231     aPtr->TraceWrapper(static_cast<JSTracer*>(aClosure), aName);
1232   }
TraceJsGcTracer1233   virtual void Trace(JS::TenuredHeap<JSObject*>* aPtr, const char* aName,
1234                      void* aClosure) const override {
1235     JS::TraceEdge(static_cast<JSTracer*>(aClosure), aPtr, aName);
1236   }
TraceJsGcTracer1237   virtual void Trace(JS::Heap<JSString*>* aPtr, const char* aName,
1238                      void* aClosure) const override {
1239     JS::TraceEdge(static_cast<JSTracer*>(aClosure), aPtr, aName);
1240   }
TraceJsGcTracer1241   virtual void Trace(JS::Heap<JSScript*>* aPtr, const char* aName,
1242                      void* aClosure) const override {
1243     JS::TraceEdge(static_cast<JSTracer*>(aClosure), aPtr, aName);
1244   }
TraceJsGcTracer1245   virtual void Trace(JS::Heap<JSFunction*>* aPtr, const char* aName,
1246                      void* aClosure) const override {
1247     JS::TraceEdge(static_cast<JSTracer*>(aClosure), aPtr, aName);
1248   }
1249 };
1250 
TraceScriptHolder(nsISupports * aHolder,JSTracer * aTracer)1251 void mozilla::TraceScriptHolder(nsISupports* aHolder, JSTracer* aTracer) {
1252   nsXPCOMCycleCollectionParticipant* participant = nullptr;
1253   CallQueryInterface(aHolder, &participant);
1254   participant->Trace(aHolder, JsGcTracer(), aTracer);
1255 }
1256 
1257 #if defined(NIGHTLY_BUILD) || defined(MOZ_DEV_EDITION) || defined(DEBUG)
1258 #  define CHECK_SINGLE_ZONE_JS_HOLDERS
1259 #endif
1260 
1261 #ifdef CHECK_SINGLE_ZONE_JS_HOLDERS
1262 
1263 // A tracer that checks that a JS holder only holds JS GC things in a single
1264 // JS::Zone.
1265 struct CheckZoneTracer : public TraceCallbacks {
1266   const char* mClassName;
1267   mutable JS::Zone* mZone;
1268 
CheckZoneTracerCheckZoneTracer1269   explicit CheckZoneTracer(const char* aClassName, JS::Zone* aZone = nullptr)
1270       : mClassName(aClassName), mZone(aZone) {}
1271 
checkZoneCheckZoneTracer1272   void checkZone(JS::Zone* aZone, const char* aName) const {
1273     if (JS::IsAtomsZone(aZone)) {
1274       // Any holder may contain pointers into the atoms zone.
1275       return;
1276     }
1277 
1278     if (!mZone) {
1279       mZone = aZone;
1280       return;
1281     }
1282 
1283     if (aZone == mZone) {
1284       return;
1285     }
1286 
1287     // Most JS holders only contain pointers to GC things in a single zone. We
1288     // group holders by referent zone where possible, allowing us to improve GC
1289     // performance by only tracing holders for zones that are being collected.
1290     //
1291     // Additionally, pointers from any holder into the atoms zone are allowed
1292     // since all holders are traced when we collect the atoms zone.
1293     //
1294     // If you added a holder that has pointers into multiple zones please try to
1295     // remedy this. Some options are:
1296     //
1297     //  - wrap all JS GC things into the same compartment
1298     //  - split GC thing pointers between separate cycle collected objects
1299     //
1300     // If all else fails, flag the class as containing pointers into multiple
1301     // zones by using NS_IMPL_CYCLE_COLLECTION_MULTI_ZONE_JSHOLDER_CLASS.
1302     MOZ_CRASH_UNSAFE_PRINTF(
1303         "JS holder %s contains pointers to GC things in more than one zone ("
1304         "found in %s)\n",
1305         mClassName, aName);
1306   }
1307 
TraceCheckZoneTracer1308   virtual void Trace(JS::Heap<JS::Value>* aPtr, const char* aName,
1309                      void* aClosure) const override {
1310     JS::Value value = aPtr->unbarrieredGet();
1311     if (value.isGCThing()) {
1312       checkZone(JS::GetGCThingZone(value.toGCCellPtr()), aName);
1313     }
1314   }
TraceCheckZoneTracer1315   virtual void Trace(JS::Heap<jsid>* aPtr, const char* aName,
1316                      void* aClosure) const override {
1317     jsid id = aPtr->unbarrieredGet();
1318     if (id.isGCThing()) {
1319       MOZ_ASSERT(JS::IsAtomsZone(JS::GetTenuredGCThingZone(id.toGCCellPtr())));
1320     }
1321   }
TraceCheckZoneTracer1322   virtual void Trace(JS::Heap<JSObject*>* aPtr, const char* aName,
1323                      void* aClosure) const override {
1324     JSObject* obj = aPtr->unbarrieredGet();
1325     if (obj) {
1326       checkZone(js::GetObjectZoneFromAnyThread(obj), aName);
1327     }
1328   }
TraceCheckZoneTracer1329   virtual void Trace(nsWrapperCache* aPtr, const char* aName,
1330                      void* aClosure) const override {
1331     JSObject* obj = aPtr->GetWrapperPreserveColor();
1332     if (obj) {
1333       checkZone(js::GetObjectZoneFromAnyThread(obj), aName);
1334     }
1335   }
TraceCheckZoneTracer1336   virtual void Trace(JS::TenuredHeap<JSObject*>* aPtr, const char* aName,
1337                      void* aClosure) const override {
1338     JSObject* obj = aPtr->unbarrieredGetPtr();
1339     if (obj) {
1340       checkZone(js::GetObjectZoneFromAnyThread(obj), aName);
1341     }
1342   }
TraceCheckZoneTracer1343   virtual void Trace(JS::Heap<JSString*>* aPtr, const char* aName,
1344                      void* aClosure) const override {
1345     JSString* str = aPtr->unbarrieredGet();
1346     if (str) {
1347       checkZone(JS::GetStringZone(str), aName);
1348     }
1349   }
TraceCheckZoneTracer1350   virtual void Trace(JS::Heap<JSScript*>* aPtr, const char* aName,
1351                      void* aClosure) const override {
1352     JSScript* script = aPtr->unbarrieredGet();
1353     if (script) {
1354       checkZone(JS::GetTenuredGCThingZone(JS::GCCellPtr(script)), aName);
1355     }
1356   }
TraceCheckZoneTracer1357   virtual void Trace(JS::Heap<JSFunction*>* aPtr, const char* aName,
1358                      void* aClosure) const override {
1359     JSFunction* fun = aPtr->unbarrieredGet();
1360     if (fun) {
1361       checkZone(js::GetObjectZoneFromAnyThread(JS_GetFunctionObject(fun)),
1362                 aName);
1363     }
1364   }
1365 };
1366 
CheckHolderIsSingleZone(void * aHolder,nsCycleCollectionParticipant * aParticipant,JS::Zone * aZone)1367 static inline void CheckHolderIsSingleZone(
1368     void* aHolder, nsCycleCollectionParticipant* aParticipant,
1369     JS::Zone* aZone) {
1370   CheckZoneTracer tracer(aParticipant->ClassName(), aZone);
1371   aParticipant->Trace(aHolder, tracer, nullptr);
1372 }
1373 
1374 #endif
1375 
ShouldCheckSingleZoneHolders()1376 static inline bool ShouldCheckSingleZoneHolders() {
1377 #if defined(DEBUG)
1378   return true;
1379 #elif defined(NIGHTLY_BUILD) || defined(MOZ_DEV_EDITION)
1380   // Don't check every time to avoid performance impact.
1381   return rand() % 256 == 0;
1382 #else
1383   return false;
1384 #endif
1385 }
1386 
TraceNativeGrayRoots(JSTracer * aTracer,JSHolderMap::WhichHolders aWhich)1387 void CycleCollectedJSRuntime::TraceNativeGrayRoots(
1388     JSTracer* aTracer, JSHolderMap::WhichHolders aWhich) {
1389   // NB: This is here just to preserve the existing XPConnect order. I doubt it
1390   // would hurt to do this after the JS holders.
1391   TraceAdditionalNativeGrayRoots(aTracer);
1392 
1393   bool checkSingleZoneHolders = ShouldCheckSingleZoneHolders();
1394   mJSHolders.ForEach(
1395       [aTracer, checkSingleZoneHolders](
1396           void* holder, nsScriptObjectTracer* tracer, JS::Zone* zone) {
1397 #ifdef CHECK_SINGLE_ZONE_JS_HOLDERS
1398         if (checkSingleZoneHolders && !tracer->IsMultiZoneJSHolder()) {
1399           CheckHolderIsSingleZone(holder, tracer, zone);
1400         }
1401 #else
1402         Unused << checkSingleZoneHolders;
1403 #endif
1404         tracer->Trace(holder, JsGcTracer(), aTracer);
1405       },
1406       aWhich);
1407 }
1408 
AddJSHolder(void * aHolder,nsScriptObjectTracer * aTracer,JS::Zone * aZone)1409 void CycleCollectedJSRuntime::AddJSHolder(void* aHolder,
1410                                           nsScriptObjectTracer* aTracer,
1411                                           JS::Zone* aZone) {
1412   mJSHolders.Put(aHolder, aTracer, aZone);
1413 }
1414 
1415 struct ClearJSHolder : public TraceCallbacks {
TraceClearJSHolder1416   virtual void Trace(JS::Heap<JS::Value>* aPtr, const char*,
1417                      void*) const override {
1418     aPtr->setUndefined();
1419   }
1420 
TraceClearJSHolder1421   virtual void Trace(JS::Heap<jsid>* aPtr, const char*, void*) const override {
1422     *aPtr = JSID_VOID;
1423   }
1424 
TraceClearJSHolder1425   virtual void Trace(JS::Heap<JSObject*>* aPtr, const char*,
1426                      void*) const override {
1427     *aPtr = nullptr;
1428   }
1429 
TraceClearJSHolder1430   virtual void Trace(nsWrapperCache* aPtr, const char* aName,
1431                      void* aClosure) const override {
1432     aPtr->ClearWrapper();
1433   }
1434 
TraceClearJSHolder1435   virtual void Trace(JS::TenuredHeap<JSObject*>* aPtr, const char*,
1436                      void*) const override {
1437     *aPtr = nullptr;
1438   }
1439 
TraceClearJSHolder1440   virtual void Trace(JS::Heap<JSString*>* aPtr, const char*,
1441                      void*) const override {
1442     *aPtr = nullptr;
1443   }
1444 
TraceClearJSHolder1445   virtual void Trace(JS::Heap<JSScript*>* aPtr, const char*,
1446                      void*) const override {
1447     *aPtr = nullptr;
1448   }
1449 
TraceClearJSHolder1450   virtual void Trace(JS::Heap<JSFunction*>* aPtr, const char*,
1451                      void*) const override {
1452     *aPtr = nullptr;
1453   }
1454 };
1455 
RemoveJSHolder(void * aHolder)1456 void CycleCollectedJSRuntime::RemoveJSHolder(void* aHolder) {
1457   nsScriptObjectTracer* tracer = mJSHolders.Extract(aHolder);
1458   if (tracer) {
1459     // Bug 1531951: The analysis can't see through the virtual call but we know
1460     // that the ClearJSHolder tracer will never GC.
1461     JS::AutoSuppressGCAnalysis nogc;
1462     tracer->Trace(aHolder, ClearJSHolder(), nullptr);
1463   }
1464 }
1465 
1466 #ifdef DEBUG
AssertNoGcThing(JS::GCCellPtr aGCThing,const char * aName,void * aClosure)1467 static void AssertNoGcThing(JS::GCCellPtr aGCThing, const char* aName,
1468                             void* aClosure) {
1469   MOZ_ASSERT(!aGCThing);
1470 }
1471 
AssertNoObjectsToTrace(void * aPossibleJSHolder)1472 void CycleCollectedJSRuntime::AssertNoObjectsToTrace(void* aPossibleJSHolder) {
1473   nsScriptObjectTracer* tracer = mJSHolders.Get(aPossibleJSHolder);
1474   if (tracer) {
1475     tracer->Trace(aPossibleJSHolder, TraceCallbackFunc(AssertNoGcThing),
1476                   nullptr);
1477   }
1478 }
1479 #endif
1480 
GCThingParticipant()1481 nsCycleCollectionParticipant* CycleCollectedJSRuntime::GCThingParticipant() {
1482   return &mGCThingCycleCollectorGlobal;
1483 }
1484 
ZoneParticipant()1485 nsCycleCollectionParticipant* CycleCollectedJSRuntime::ZoneParticipant() {
1486   return &mJSZoneCycleCollectorGlobal;
1487 }
1488 
TraverseRoots(nsCycleCollectionNoteRootCallback & aCb)1489 nsresult CycleCollectedJSRuntime::TraverseRoots(
1490     nsCycleCollectionNoteRootCallback& aCb) {
1491   TraverseNativeRoots(aCb);
1492 
1493   NoteWeakMapsTracer trc(mJSRuntime, aCb);
1494   js::TraceWeakMaps(&trc);
1495 
1496   return NS_OK;
1497 }
1498 
UsefulToMergeZones() const1499 bool CycleCollectedJSRuntime::UsefulToMergeZones() const { return false; }
1500 
FixWeakMappingGrayBits() const1501 void CycleCollectedJSRuntime::FixWeakMappingGrayBits() const {
1502   MOZ_ASSERT(!JS::IsIncrementalGCInProgress(mJSRuntime),
1503              "Don't call FixWeakMappingGrayBits during a GC.");
1504   FixWeakMappingGrayBitsTracer fixer(mJSRuntime);
1505   fixer.FixAll();
1506 }
1507 
CheckGrayBits() const1508 void CycleCollectedJSRuntime::CheckGrayBits() const {
1509   MOZ_ASSERT(!JS::IsIncrementalGCInProgress(mJSRuntime),
1510              "Don't call CheckGrayBits during a GC.");
1511 
1512 #ifndef ANDROID
1513   // Bug 1346874 - The gray state check is expensive. Android tests are already
1514   // slow enough that this check can easily push them over the threshold to a
1515   // timeout.
1516 
1517   MOZ_ASSERT(js::CheckGrayMarkingState(mJSRuntime));
1518   MOZ_ASSERT(CheckWeakMappingGrayBitsTracer::Check(mJSRuntime));
1519 #endif
1520 }
1521 
AreGCGrayBitsValid() const1522 bool CycleCollectedJSRuntime::AreGCGrayBitsValid() const {
1523   return js::AreGCGrayBitsValid(mJSRuntime);
1524 }
1525 
GarbageCollect(JS::GCReason aReason) const1526 void CycleCollectedJSRuntime::GarbageCollect(JS::GCReason aReason) const {
1527   JSContext* cx = CycleCollectedJSContext::Get()->Context();
1528   JS::PrepareForFullGC(cx);
1529   JS::NonIncrementalGC(cx, JS::GCOptions::Normal, aReason);
1530 }
1531 
JSObjectsTenured()1532 void CycleCollectedJSRuntime::JSObjectsTenured() {
1533   JSContext* cx = CycleCollectedJSContext::Get()->Context();
1534   for (auto iter = mNurseryObjects.Iter(); !iter.Done(); iter.Next()) {
1535     nsWrapperCache* cache = iter.Get();
1536     JSObject* wrapper = cache->GetWrapperMaybeDead();
1537     MOZ_DIAGNOSTIC_ASSERT(wrapper);
1538     if (!JS::ObjectIsTenured(wrapper)) {
1539       MOZ_ASSERT(!cache->PreservingWrapper());
1540       js::gc::FinalizeDeadNurseryObject(cx, wrapper);
1541     }
1542   }
1543 
1544 #ifdef DEBUG
1545   for (auto iter = mPreservedNurseryObjects.Iter(); !iter.Done(); iter.Next()) {
1546     MOZ_ASSERT(JS::ObjectIsTenured(iter.Get().get()));
1547   }
1548 #endif
1549 
1550   mNurseryObjects.Clear();
1551   mPreservedNurseryObjects.Clear();
1552 }
1553 
NurseryWrapperAdded(nsWrapperCache * aCache)1554 void CycleCollectedJSRuntime::NurseryWrapperAdded(nsWrapperCache* aCache) {
1555   MOZ_ASSERT(aCache);
1556   MOZ_ASSERT(aCache->GetWrapperMaybeDead());
1557   MOZ_ASSERT(!JS::ObjectIsTenured(aCache->GetWrapperMaybeDead()));
1558   mNurseryObjects.InfallibleAppend(aCache);
1559 }
1560 
NurseryWrapperPreserved(JSObject * aWrapper)1561 void CycleCollectedJSRuntime::NurseryWrapperPreserved(JSObject* aWrapper) {
1562   mPreservedNurseryObjects.InfallibleAppend(
1563       JS::PersistentRooted<JSObject*>(mJSRuntime, aWrapper));
1564 }
1565 
DeferredFinalize(DeferredFinalizeAppendFunction aAppendFunc,DeferredFinalizeFunction aFunc,void * aThing)1566 void CycleCollectedJSRuntime::DeferredFinalize(
1567     DeferredFinalizeAppendFunction aAppendFunc, DeferredFinalizeFunction aFunc,
1568     void* aThing) {
1569   // Tell the analysis that the function pointers will not GC.
1570   JS::AutoSuppressGCAnalysis suppress;
1571   mDeferredFinalizerTable.WithEntryHandle(aFunc, [&](auto&& entry) {
1572     if (entry) {
1573       aAppendFunc(entry.Data(), aThing);
1574     } else {
1575       entry.Insert(aAppendFunc(nullptr, aThing));
1576     }
1577   });
1578 }
1579 
DeferredFinalize(nsISupports * aSupports)1580 void CycleCollectedJSRuntime::DeferredFinalize(nsISupports* aSupports) {
1581   typedef DeferredFinalizerImpl<nsISupports> Impl;
1582   DeferredFinalize(Impl::AppendDeferredFinalizePointer, Impl::DeferredFinalize,
1583                    aSupports);
1584 }
1585 
DumpJSHeap(FILE * aFile)1586 void CycleCollectedJSRuntime::DumpJSHeap(FILE* aFile) {
1587   JSContext* cx = CycleCollectedJSContext::Get()->Context();
1588 
1589   mozilla::MallocSizeOf mallocSizeOf =
1590       PR_GetEnv("MOZ_GC_LOG_SIZE") ? moz_malloc_size_of : nullptr;
1591   js::DumpHeap(cx, aFile, js::CollectNurseryBeforeDump, mallocSizeOf);
1592 }
1593 
IncrementalFinalizeRunnable(CycleCollectedJSRuntime * aRt,DeferredFinalizerTable & aFinalizers)1594 IncrementalFinalizeRunnable::IncrementalFinalizeRunnable(
1595     CycleCollectedJSRuntime* aRt, DeferredFinalizerTable& aFinalizers)
1596     : DiscardableRunnable("IncrementalFinalizeRunnable"),
1597       mRuntime(aRt),
1598       mFinalizeFunctionToRun(0),
1599       mReleasing(false) {
1600   for (auto iter = aFinalizers.Iter(); !iter.Done(); iter.Next()) {
1601     DeferredFinalizeFunction& function = iter.Key();
1602     void*& data = iter.Data();
1603 
1604     DeferredFinalizeFunctionHolder* holder =
1605         mDeferredFinalizeFunctions.AppendElement();
1606     holder->run = function;
1607     holder->data = data;
1608 
1609     iter.Remove();
1610   }
1611   MOZ_ASSERT(mDeferredFinalizeFunctions.Length());
1612 }
1613 
~IncrementalFinalizeRunnable()1614 IncrementalFinalizeRunnable::~IncrementalFinalizeRunnable() {
1615   MOZ_ASSERT(!mDeferredFinalizeFunctions.Length());
1616   MOZ_ASSERT(!mRuntime);
1617 }
1618 
ReleaseNow(bool aLimited)1619 void IncrementalFinalizeRunnable::ReleaseNow(bool aLimited) {
1620   if (mReleasing) {
1621     NS_WARNING("Re-entering ReleaseNow");
1622     return;
1623   }
1624   {
1625     AUTO_PROFILER_LABEL("IncrementalFinalizeRunnable::ReleaseNow",
1626                         GCCC_Finalize);
1627 
1628     mozilla::AutoRestore<bool> ar(mReleasing);
1629     mReleasing = true;
1630     MOZ_ASSERT(mDeferredFinalizeFunctions.Length() != 0,
1631                "We should have at least ReleaseSliceNow to run");
1632     MOZ_ASSERT(mFinalizeFunctionToRun < mDeferredFinalizeFunctions.Length(),
1633                "No more finalizers to run?");
1634 
1635     TimeDuration sliceTime = TimeDuration::FromMilliseconds(SliceMillis);
1636     TimeStamp started = aLimited ? TimeStamp::Now() : TimeStamp();
1637     bool timeout = false;
1638     do {
1639       const DeferredFinalizeFunctionHolder& function =
1640           mDeferredFinalizeFunctions[mFinalizeFunctionToRun];
1641       if (aLimited) {
1642         bool done = false;
1643         while (!timeout && !done) {
1644           /*
1645            * We don't want to read the clock too often, so we try to
1646            * release slices of 100 items.
1647            */
1648           done = function.run(100, function.data);
1649           timeout = TimeStamp::Now() - started >= sliceTime;
1650         }
1651         if (done) {
1652           ++mFinalizeFunctionToRun;
1653         }
1654         if (timeout) {
1655           break;
1656         }
1657       } else {
1658         while (!function.run(UINT32_MAX, function.data))
1659           ;
1660         ++mFinalizeFunctionToRun;
1661       }
1662     } while (mFinalizeFunctionToRun < mDeferredFinalizeFunctions.Length());
1663   }
1664 
1665   if (mFinalizeFunctionToRun == mDeferredFinalizeFunctions.Length()) {
1666     MOZ_ASSERT(mRuntime->mFinalizeRunnable == this);
1667     mDeferredFinalizeFunctions.Clear();
1668     CycleCollectedJSRuntime* runtime = mRuntime;
1669     mRuntime = nullptr;
1670     // NB: This may delete this!
1671     runtime->mFinalizeRunnable = nullptr;
1672   }
1673 }
1674 
1675 NS_IMETHODIMP
Run()1676 IncrementalFinalizeRunnable::Run() {
1677   if (!mDeferredFinalizeFunctions.Length()) {
1678     /* These items were already processed synchronously in JSGC_END. */
1679     MOZ_ASSERT(!mRuntime);
1680     return NS_OK;
1681   }
1682 
1683   MOZ_ASSERT(mRuntime->mFinalizeRunnable == this);
1684   TimeStamp start = TimeStamp::Now();
1685   ReleaseNow(true);
1686 
1687   if (mDeferredFinalizeFunctions.Length()) {
1688     nsresult rv = NS_DispatchToCurrentThread(this);
1689     if (NS_FAILED(rv)) {
1690       ReleaseNow(false);
1691     }
1692   } else {
1693     MOZ_ASSERT(!mRuntime);
1694   }
1695 
1696   uint32_t duration = (uint32_t)((TimeStamp::Now() - start).ToMilliseconds());
1697   Telemetry::Accumulate(Telemetry::DEFERRED_FINALIZE_ASYNC, duration);
1698 
1699   return NS_OK;
1700 }
1701 
FinalizeDeferredThings(CycleCollectedJSContext::DeferredFinalizeType aType)1702 void CycleCollectedJSRuntime::FinalizeDeferredThings(
1703     CycleCollectedJSContext::DeferredFinalizeType aType) {
1704   /*
1705    * If the previous GC created a runnable to finalize objects
1706    * incrementally, and if it hasn't finished yet, finish it now. We
1707    * don't want these to build up. We also don't want to allow any
1708    * existing incremental finalize runnables to run after a
1709    * non-incremental GC, since they are often used to detect leaks.
1710    */
1711   if (mFinalizeRunnable) {
1712     mFinalizeRunnable->ReleaseNow(false);
1713     if (mFinalizeRunnable) {
1714       // If we re-entered ReleaseNow, we couldn't delete mFinalizeRunnable and
1715       // we need to just continue processing it.
1716       return;
1717     }
1718   }
1719 
1720   if (mDeferredFinalizerTable.Count() == 0) {
1721     return;
1722   }
1723 
1724   mFinalizeRunnable =
1725       new IncrementalFinalizeRunnable(this, mDeferredFinalizerTable);
1726 
1727   // Everything should be gone now.
1728   MOZ_ASSERT(mDeferredFinalizerTable.Count() == 0);
1729 
1730   if (aType == CycleCollectedJSContext::FinalizeIncrementally) {
1731     NS_DispatchToCurrentThreadQueue(do_AddRef(mFinalizeRunnable), 2500,
1732                                     EventQueuePriority::Idle);
1733   } else {
1734     mFinalizeRunnable->ReleaseNow(false);
1735     MOZ_ASSERT(!mFinalizeRunnable);
1736   }
1737 }
1738 
OOMStateToString(const OOMState aOomState) const1739 const char* CycleCollectedJSRuntime::OOMStateToString(
1740     const OOMState aOomState) const {
1741   switch (aOomState) {
1742     case OOMState::OK:
1743       return "OK";
1744     case OOMState::Reporting:
1745       return "Reporting";
1746     case OOMState::Reported:
1747       return "Reported";
1748     case OOMState::Recovered:
1749       return "Recovered";
1750     default:
1751       MOZ_ASSERT_UNREACHABLE("OOMState holds an invalid value");
1752       return "Unknown";
1753   }
1754 }
1755 
AnnotateAndSetOutOfMemory(OOMState * aStatePtr,OOMState aNewState)1756 void CycleCollectedJSRuntime::AnnotateAndSetOutOfMemory(OOMState* aStatePtr,
1757                                                         OOMState aNewState) {
1758   *aStatePtr = aNewState;
1759   CrashReporter::Annotation annotation =
1760       (aStatePtr == &mOutOfMemoryState)
1761           ? CrashReporter::Annotation::JSOutOfMemory
1762           : CrashReporter::Annotation::JSLargeAllocationFailure;
1763 
1764   CrashReporter::AnnotateCrashReport(
1765       annotation, nsDependentCString(OOMStateToString(aNewState)));
1766 }
1767 
OnGC(JSContext * aContext,JSGCStatus aStatus,JS::GCReason aReason)1768 void CycleCollectedJSRuntime::OnGC(JSContext* aContext, JSGCStatus aStatus,
1769                                    JS::GCReason aReason) {
1770   switch (aStatus) {
1771     case JSGC_BEGIN:
1772       nsCycleCollector_prepareForGarbageCollection();
1773       PrepareWaitingZonesForGC();
1774       break;
1775     case JSGC_END: {
1776       if (mOutOfMemoryState == OOMState::Reported) {
1777         AnnotateAndSetOutOfMemory(&mOutOfMemoryState, OOMState::Recovered);
1778       }
1779       if (mLargeAllocationFailureState == OOMState::Reported) {
1780         AnnotateAndSetOutOfMemory(&mLargeAllocationFailureState,
1781                                   OOMState::Recovered);
1782       }
1783 
1784       // Do any deferred finalization of native objects. We will run the
1785       // finalizer later after we've returned to the event loop if any of
1786       // three conditions hold:
1787       // a) The GC is incremental. In this case, we probably care about pauses.
1788       // b) There is a pending exception. The finalizers are not set up to run
1789       // in that state.
1790       // c) The GC was triggered for internal JS engine reasons. If this is the
1791       // case, then we may be in the middle of running some code that the JIT
1792       // has assumed can't have certain kinds of side effects. Finalizers can do
1793       // all sorts of things, such as run JS, so we want to run them later.
1794       // However, if we're shutting down, we need to destroy things immediately.
1795       //
1796       // Why do we ever bother finalizing things immediately if that's so
1797       // questionable? In some situations, such as while testing or in low
1798       // memory situations, we really want to free things right away.
1799       bool finalizeIncrementally = JS::WasIncrementalGC(mJSRuntime) ||
1800                                    JS_IsExceptionPending(aContext) ||
1801                                    (JS::InternalGCReason(aReason) &&
1802                                     aReason != JS::GCReason::DESTROY_RUNTIME);
1803 
1804       FinalizeDeferredThings(
1805           finalizeIncrementally ? CycleCollectedJSContext::FinalizeIncrementally
1806                                 : CycleCollectedJSContext::FinalizeNow);
1807 
1808       break;
1809     }
1810     default:
1811       MOZ_CRASH();
1812   }
1813 
1814   CustomGCCallback(aStatus);
1815 }
1816 
OnOutOfMemory()1817 void CycleCollectedJSRuntime::OnOutOfMemory() {
1818   AnnotateAndSetOutOfMemory(&mOutOfMemoryState, OOMState::Reporting);
1819   CustomOutOfMemoryCallback();
1820   AnnotateAndSetOutOfMemory(&mOutOfMemoryState, OOMState::Reported);
1821 }
1822 
SetLargeAllocationFailure(OOMState aNewState)1823 void CycleCollectedJSRuntime::SetLargeAllocationFailure(OOMState aNewState) {
1824   AnnotateAndSetOutOfMemory(&mLargeAllocationFailureState, aNewState);
1825 }
1826 
PrepareWaitingZonesForGC()1827 void CycleCollectedJSRuntime::PrepareWaitingZonesForGC() {
1828   JSContext* cx = CycleCollectedJSContext::Get()->Context();
1829   if (mZonesWaitingForGC.Count() == 0) {
1830     JS::PrepareForFullGC(cx);
1831   } else {
1832     for (const auto& key : mZonesWaitingForGC) {
1833       JS::PrepareZoneForGC(cx, key);
1834     }
1835     mZonesWaitingForGC.Clear();
1836   }
1837 }
1838 
1839 /* static */
OnZoneDestroyed(JSFreeOp * aFop,JS::Zone * aZone)1840 void CycleCollectedJSRuntime::OnZoneDestroyed(JSFreeOp* aFop, JS::Zone* aZone) {
1841   // Remove the zone from the set of zones waiting for GC, if present. This can
1842   // happen if a zone is added to the set during an incremental GC in which it
1843   // is later destroyed.
1844   CycleCollectedJSRuntime* runtime = Get();
1845   runtime->mZonesWaitingForGC.Remove(aZone);
1846 }
1847 
invoke(JS::HandleObject global,js::ScriptEnvironmentPreparer::Closure & closure)1848 void CycleCollectedJSRuntime::EnvironmentPreparer::invoke(
1849     JS::HandleObject global, js::ScriptEnvironmentPreparer::Closure& closure) {
1850   MOZ_ASSERT(JS_IsGlobalObject(global));
1851   nsIGlobalObject* nativeGlobal = xpc::NativeGlobal(global);
1852 
1853   // Not much we can do if we simply don't have a usable global here...
1854   NS_ENSURE_TRUE_VOID(nativeGlobal && nativeGlobal->HasJSGlobal());
1855 
1856   AutoEntryScript aes(nativeGlobal, "JS-engine-initiated execution");
1857 
1858   MOZ_ASSERT(!JS_IsExceptionPending(aes.cx()));
1859 
1860   DebugOnly<bool> ok = closure(aes.cx());
1861 
1862   MOZ_ASSERT_IF(ok, !JS_IsExceptionPending(aes.cx()));
1863 
1864   // The AutoEntryScript will check for JS_IsExceptionPending on the
1865   // JSContext and report it as needed as it comes off the stack.
1866 }
1867 
1868 /* static */
Get()1869 CycleCollectedJSRuntime* CycleCollectedJSRuntime::Get() {
1870   auto context = CycleCollectedJSContext::Get();
1871   if (context) {
1872     return context->Runtime();
1873   }
1874   return nullptr;
1875 }
1876 
1877 #ifdef MOZ_JS_DEV_ERROR_INTERCEPTOR
1878 
1879 namespace js {
1880 extern void DumpValue(const JS::Value& val);
1881 }
1882 
Shutdown(JSRuntime * rt)1883 void CycleCollectedJSRuntime::ErrorInterceptor::Shutdown(JSRuntime* rt) {
1884   JS_SetErrorInterceptorCallback(rt, nullptr);
1885   mThrownError.reset();
1886 }
1887 
1888 /* virtual */
interceptError(JSContext * cx,JS::HandleValue exn)1889 void CycleCollectedJSRuntime::ErrorInterceptor::interceptError(
1890     JSContext* cx, JS::HandleValue exn) {
1891   if (mThrownError) {
1892     // We already have an error, we don't need anything more.
1893     return;
1894   }
1895 
1896   if (!nsContentUtils::ThreadsafeIsSystemCaller(cx)) {
1897     // We are only interested in chrome code.
1898     return;
1899   }
1900 
1901   const auto type = JS_GetErrorType(exn);
1902   if (!type) {
1903     // This is not one of the primitive error types.
1904     return;
1905   }
1906 
1907   switch (*type) {
1908     case JSExnType::JSEXN_REFERENCEERR:
1909     case JSExnType::JSEXN_SYNTAXERR:
1910       break;
1911     default:
1912       // Not one of the errors we are interested in.
1913       // Note that we are not interested in instances of `TypeError`
1914       // for the time being, as DOM (ab)uses this constructor to represent
1915       // all sorts of errors that are not even remotely related to type
1916       // errors (e.g. some network errors).
1917       // If we ever have a mechanism to differentiate between DOM-thrown
1918       // and SpiderMonkey-thrown instances of `TypeError`, we should
1919       // consider watching for `TypeError` here.
1920       return;
1921   }
1922 
1923   // Now copy the details of the exception locally.
1924   // While copying the details of an exception could be expensive, in most runs,
1925   // this will be done at most once during the execution of the process, so the
1926   // total cost should be reasonable.
1927 
1928   ErrorDetails details;
1929   details.mType = *type;
1930   // If `exn` isn't an exception object, `ExtractErrorValues` could end up
1931   // calling `toString()`, which could in turn end up throwing an error. While
1932   // this should work, we want to avoid that complex use case. Fortunately, we
1933   // have already checked above that `exn` is an exception object, so nothing
1934   // such should happen.
1935   nsContentUtils::ExtractErrorValues(cx, exn, details.mFilename, &details.mLine,
1936                                      &details.mColumn, details.mMessage);
1937 
1938   JS::UniqueChars buf =
1939       JS::FormatStackDump(cx, /* showArgs = */ false, /* showLocals = */ false,
1940                           /* showThisProps = */ false);
1941   CopyUTF8toUTF16(mozilla::MakeStringSpan(buf.get()), details.mStack);
1942 
1943   mThrownError.emplace(std::move(details));
1944 }
1945 
ClearRecentDevError()1946 void CycleCollectedJSRuntime::ClearRecentDevError() {
1947   mErrorInterceptor.mThrownError.reset();
1948 }
1949 
GetRecentDevError(JSContext * cx,JS::MutableHandle<JS::Value> error)1950 bool CycleCollectedJSRuntime::GetRecentDevError(
1951     JSContext* cx, JS::MutableHandle<JS::Value> error) {
1952   if (!mErrorInterceptor.mThrownError) {
1953     return true;
1954   }
1955 
1956   // Create a copy of the exception.
1957   JS::RootedObject obj(cx, JS_NewPlainObject(cx));
1958   if (!obj) {
1959     return false;
1960   }
1961 
1962   JS::RootedValue message(cx);
1963   JS::RootedValue filename(cx);
1964   JS::RootedValue stack(cx);
1965   if (!ToJSValue(cx, mErrorInterceptor.mThrownError->mMessage, &message) ||
1966       !ToJSValue(cx, mErrorInterceptor.mThrownError->mFilename, &filename) ||
1967       !ToJSValue(cx, mErrorInterceptor.mThrownError->mStack, &stack)) {
1968     return false;
1969   }
1970 
1971   // Build the object.
1972   const auto FLAGS = JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT;
1973   if (!JS_DefineProperty(cx, obj, "message", message, FLAGS) ||
1974       !JS_DefineProperty(cx, obj, "fileName", filename, FLAGS) ||
1975       !JS_DefineProperty(cx, obj, "lineNumber",
1976                          mErrorInterceptor.mThrownError->mLine, FLAGS) ||
1977       !JS_DefineProperty(cx, obj, "stack", stack, FLAGS)) {
1978     return false;
1979   }
1980 
1981   // Pass the result.
1982   error.setObject(*obj);
1983   return true;
1984 }
1985 #endif  // MOZ_JS_DEV_ERROR_INTERCEPTOR
1986 
1987 #undef MOZ_JS_DEV_ERROR_INTERCEPTOR
1988