Line data Source code
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
19 : // collection, held by us (see TraceNativeGrayRoots). Roots from this
20 : // category are considered grey in the cycle collector; whether or not
21 : // they are collected 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)
27 : // when tracing inside TraceNativeGrayRoots. This allows us to walk those
28 : // roots later on and add all objects reachable only from them to the
29 : // cycle collector.
30 : //
31 : // Phases:
32 : //
33 : // 1. marking of the roots in category 1 by having the JS GC do its marking
34 : // 2. marking of the roots in category 2 by having the JS GC call us back
35 : // (via JS_SetExtraGCRootsTracer) and running TraceNativeBlackRoots
36 : // 3. marking of the roots in category 3 by TraceNativeGrayRoots using an
37 : // additional color (gray).
38 : // 4. end of GC, GC can sweep its heap
39 : //
40 : // At some later point, when the cycle collector runs:
41 : //
42 : // 5. walk gray objects and add them to the cycle collector, cycle collect
43 : //
44 : // JS objects that are part of cycles the cycle collector breaks will be
45 : // collected by the next JS GC.
46 : //
47 : // If WantAllTraces() is false the cycle collector will not traverse roots
48 : // from category 1 or any JS objects held by them. Any JS objects they hold
49 : // will already be marked by the JS GC and will thus be colored black
50 : // themselves. Any C++ objects they hold will have a missing (untraversed)
51 : // edge from the JS object to the C++ object and so it will be marked black
52 : // too. This decreases the number of objects that the cycle collector has to
53 : // deal with.
54 : // To improve debugging, if WantAllTraces() is true all JS objects are
55 : // traversed.
56 :
57 : #include "mozilla/CycleCollectedJSRuntime.h"
58 : #include <algorithm>
59 : #include "mozilla/ArrayUtils.h"
60 : #include "mozilla/AutoRestore.h"
61 : #include "mozilla/CycleCollectedJSContext.h"
62 : #include "mozilla/Move.h"
63 : #include "mozilla/MemoryReporting.h"
64 : #include "mozilla/Sprintf.h"
65 : #include "mozilla/Telemetry.h"
66 : #include "mozilla/TimelineConsumers.h"
67 : #include "mozilla/TimelineMarker.h"
68 : #include "mozilla/Unused.h"
69 : #include "mozilla/DebuggerOnGCRunnable.h"
70 : #include "mozilla/dom/DOMJSClass.h"
71 : #include "mozilla/dom/ProfileTimelineMarkerBinding.h"
72 : #include "mozilla/dom/Promise.h"
73 : #include "mozilla/dom/PromiseBinding.h"
74 : #include "mozilla/dom/PromiseDebugging.h"
75 : #include "mozilla/dom/ScriptSettings.h"
76 : #include "jsprf.h"
77 : #include "js/Debug.h"
78 : #include "js/GCAPI.h"
79 : #include "nsContentUtils.h"
80 : #include "nsCycleCollectionNoteRootCallback.h"
81 : #include "nsCycleCollectionParticipant.h"
82 : #include "nsCycleCollector.h"
83 : #include "nsDOMJSUtils.h"
84 : #include "nsJSUtils.h"
85 : #include "nsWrapperCache.h"
86 : #include "nsStringBuffer.h"
87 : #include "GeckoProfiler.h"
88 : #include "ProfilerMarkerPayload.h"
89 :
90 : #ifdef MOZ_CRASHREPORTER
91 : #include "nsExceptionHandler.h"
92 : #endif
93 :
94 : #include "nsIException.h"
95 : #include "nsIPlatformInfo.h"
96 : #include "nsThread.h"
97 : #include "nsThreadUtils.h"
98 : #include "xpcpublic.h"
99 :
100 : using namespace mozilla;
101 : using namespace mozilla::dom;
102 :
103 : namespace mozilla {
104 :
105 : struct DeferredFinalizeFunctionHolder
106 : {
107 : DeferredFinalizeFunction run;
108 : void* data;
109 : };
110 :
111 : class IncrementalFinalizeRunnable : public Runnable
112 : {
113 : typedef AutoTArray<DeferredFinalizeFunctionHolder, 16> DeferredFinalizeArray;
114 : typedef CycleCollectedJSRuntime::DeferredFinalizerTable DeferredFinalizerTable;
115 :
116 : CycleCollectedJSRuntime* mRuntime;
117 : DeferredFinalizeArray mDeferredFinalizeFunctions;
118 : uint32_t mFinalizeFunctionToRun;
119 : bool mReleasing;
120 :
121 : static const PRTime SliceMillis = 5; /* ms */
122 :
123 : public:
124 : IncrementalFinalizeRunnable(CycleCollectedJSRuntime* aRt,
125 : DeferredFinalizerTable& aFinalizerTable);
126 : virtual ~IncrementalFinalizeRunnable();
127 :
128 : void ReleaseNow(bool aLimited);
129 :
130 : NS_DECL_NSIRUNNABLE
131 : };
132 :
133 : } // namespace mozilla
134 :
135 : struct NoteWeakMapChildrenTracer : public JS::CallbackTracer
136 : {
137 0 : NoteWeakMapChildrenTracer(JSRuntime* aRt,
138 : nsCycleCollectionNoteRootCallback& aCb)
139 0 : : JS::CallbackTracer(aRt), mCb(aCb), mTracedAny(false), mMap(nullptr),
140 0 : mKey(nullptr), mKeyDelegate(nullptr)
141 : {
142 0 : }
143 : void onChild(const JS::GCCellPtr& aThing) override;
144 : nsCycleCollectionNoteRootCallback& mCb;
145 : bool mTracedAny;
146 : JSObject* mMap;
147 : JS::GCCellPtr mKey;
148 : JSObject* mKeyDelegate;
149 : };
150 :
151 : void
152 0 : NoteWeakMapChildrenTracer::onChild(const JS::GCCellPtr& aThing)
153 : {
154 0 : if (aThing.is<JSString>()) {
155 0 : return;
156 : }
157 :
158 0 : if (!JS::GCThingIsMarkedGray(aThing) && !mCb.WantAllTraces()) {
159 0 : return;
160 : }
161 :
162 0 : if (AddToCCKind(aThing.kind())) {
163 0 : mCb.NoteWeakMapping(mMap, mKey, mKeyDelegate, aThing);
164 0 : mTracedAny = true;
165 : } else {
166 0 : JS::TraceChildren(this, aThing);
167 : }
168 : }
169 :
170 : struct NoteWeakMapsTracer : public js::WeakMapTracer
171 : {
172 0 : NoteWeakMapsTracer(JSRuntime* aRt, nsCycleCollectionNoteRootCallback& aCccb)
173 0 : : js::WeakMapTracer(aRt), mCb(aCccb), mChildTracer(aRt, aCccb)
174 : {
175 0 : }
176 : void trace(JSObject* aMap, JS::GCCellPtr aKey, JS::GCCellPtr aValue) override;
177 : nsCycleCollectionNoteRootCallback& mCb;
178 : NoteWeakMapChildrenTracer mChildTracer;
179 : };
180 :
181 : void
182 0 : NoteWeakMapsTracer::trace(JSObject* aMap, JS::GCCellPtr aKey,
183 : JS::GCCellPtr aValue)
184 : {
185 : // If nothing that could be held alive by this entry is marked gray, return.
186 0 : if ((!aKey || !JS::GCThingIsMarkedGray(aKey)) &&
187 0 : MOZ_LIKELY(!mCb.WantAllTraces())) {
188 0 : if (!aValue || !JS::GCThingIsMarkedGray(aValue) || aValue.is<JSString>()) {
189 0 : return;
190 : }
191 : }
192 :
193 : // The cycle collector can only properly reason about weak maps if it can
194 : // reason about the liveness of their keys, which in turn requires that
195 : // the key can be represented in the cycle collector graph. All existing
196 : // uses of weak maps use either objects or scripts as keys, which are okay.
197 0 : MOZ_ASSERT(AddToCCKind(aKey.kind()));
198 :
199 : // As an emergency fallback for non-debug builds, if the key is not
200 : // representable in the cycle collector graph, we treat it as marked. This
201 : // can cause leaks, but is preferable to ignoring the binding, which could
202 : // cause the cycle collector to free live objects.
203 0 : if (!AddToCCKind(aKey.kind())) {
204 0 : aKey = nullptr;
205 : }
206 :
207 0 : JSObject* kdelegate = nullptr;
208 0 : if (aKey.is<JSObject>()) {
209 0 : kdelegate = js::GetWeakmapKeyDelegate(&aKey.as<JSObject>());
210 : }
211 :
212 0 : if (AddToCCKind(aValue.kind())) {
213 0 : mCb.NoteWeakMapping(aMap, aKey, kdelegate, aValue);
214 : } else {
215 0 : mChildTracer.mTracedAny = false;
216 0 : mChildTracer.mMap = aMap;
217 0 : mChildTracer.mKey = aKey;
218 0 : mChildTracer.mKeyDelegate = kdelegate;
219 :
220 0 : if (!aValue.is<JSString>()) {
221 0 : JS::TraceChildren(&mChildTracer, aValue);
222 : }
223 :
224 : // The delegate could hold alive the key, so report something to the CC
225 : // if we haven't already.
226 0 : if (!mChildTracer.mTracedAny &&
227 0 : aKey && JS::GCThingIsMarkedGray(aKey) && kdelegate) {
228 0 : mCb.NoteWeakMapping(aMap, aKey, kdelegate, nullptr);
229 : }
230 : }
231 : }
232 :
233 : // Report whether the key or value of a weak mapping entry are gray but need to
234 : // be marked black.
235 : static void
236 0 : ShouldWeakMappingEntryBeBlack(JSObject* aMap, JS::GCCellPtr aKey, JS::GCCellPtr aValue,
237 : bool* aKeyShouldBeBlack, bool* aValueShouldBeBlack)
238 : {
239 0 : *aKeyShouldBeBlack = false;
240 0 : *aValueShouldBeBlack = false;
241 :
242 : // If nothing that could be held alive by this entry is marked gray, return.
243 0 : bool keyMightNeedMarking = aKey && JS::GCThingIsMarkedGray(aKey);
244 0 : bool valueMightNeedMarking = aValue && JS::GCThingIsMarkedGray(aValue) &&
245 0 : aValue.kind() != JS::TraceKind::String;
246 0 : if (!keyMightNeedMarking && !valueMightNeedMarking) {
247 0 : return;
248 : }
249 :
250 0 : if (!AddToCCKind(aKey.kind())) {
251 0 : aKey = nullptr;
252 : }
253 :
254 0 : if (keyMightNeedMarking && aKey.is<JSObject>()) {
255 0 : JSObject* kdelegate = js::GetWeakmapKeyDelegate(&aKey.as<JSObject>());
256 0 : if (kdelegate && !JS::ObjectIsMarkedGray(kdelegate) &&
257 0 : (!aMap || !JS::ObjectIsMarkedGray(aMap)))
258 : {
259 0 : *aKeyShouldBeBlack = true;
260 : }
261 : }
262 :
263 0 : if (aValue && JS::GCThingIsMarkedGray(aValue) &&
264 0 : (!aKey || !JS::GCThingIsMarkedGray(aKey)) &&
265 0 : (!aMap || !JS::ObjectIsMarkedGray(aMap)) &&
266 0 : aValue.kind() != JS::TraceKind::Shape) {
267 0 : *aValueShouldBeBlack = true;
268 : }
269 : }
270 :
271 : struct FixWeakMappingGrayBitsTracer : public js::WeakMapTracer
272 : {
273 0 : explicit FixWeakMappingGrayBitsTracer(JSRuntime* aRt)
274 0 : : js::WeakMapTracer(aRt)
275 : {
276 0 : }
277 :
278 : void
279 0 : FixAll()
280 : {
281 0 : do {
282 0 : mAnyMarked = false;
283 0 : js::TraceWeakMaps(this);
284 0 : } while (mAnyMarked);
285 0 : }
286 :
287 0 : void trace(JSObject* aMap, JS::GCCellPtr aKey, JS::GCCellPtr aValue) override
288 : {
289 : bool keyShouldBeBlack;
290 : bool valueShouldBeBlack;
291 : ShouldWeakMappingEntryBeBlack(aMap, aKey, aValue,
292 0 : &keyShouldBeBlack, &valueShouldBeBlack);
293 0 : if (keyShouldBeBlack && JS::UnmarkGrayGCThingRecursively(aKey)) {
294 0 : mAnyMarked = true;
295 : }
296 :
297 0 : if (valueShouldBeBlack && JS::UnmarkGrayGCThingRecursively(aValue)) {
298 0 : mAnyMarked = true;
299 : }
300 0 : }
301 :
302 : MOZ_INIT_OUTSIDE_CTOR bool mAnyMarked;
303 : };
304 :
305 : #ifdef DEBUG
306 : // Check whether weak maps are marked correctly according to the logic above.
307 : struct CheckWeakMappingGrayBitsTracer : public js::WeakMapTracer
308 : {
309 0 : explicit CheckWeakMappingGrayBitsTracer(JSRuntime* aRt)
310 0 : : js::WeakMapTracer(aRt), mFailed(false)
311 : {
312 0 : }
313 :
314 : static bool
315 0 : Check(JSRuntime* aRt)
316 : {
317 0 : CheckWeakMappingGrayBitsTracer tracer(aRt);
318 0 : js::TraceWeakMaps(&tracer);
319 0 : return !tracer.mFailed;
320 : }
321 :
322 0 : void trace(JSObject* aMap, JS::GCCellPtr aKey, JS::GCCellPtr aValue) override
323 : {
324 : bool keyShouldBeBlack;
325 : bool valueShouldBeBlack;
326 : ShouldWeakMappingEntryBeBlack(aMap, aKey, aValue,
327 0 : &keyShouldBeBlack, &valueShouldBeBlack);
328 :
329 0 : if (keyShouldBeBlack) {
330 0 : fprintf(stderr, "Weak mapping key %p of map %p should be black\n",
331 0 : aKey.asCell(), aMap);
332 0 : mFailed = true;
333 : }
334 :
335 0 : if (valueShouldBeBlack) {
336 0 : fprintf(stderr, "Weak mapping value %p of map %p should be black\n",
337 0 : aValue.asCell(), aMap);
338 0 : mFailed = true;
339 : }
340 0 : }
341 :
342 : bool mFailed;
343 : };
344 : #endif // DEBUG
345 :
346 : static void
347 0 : CheckParticipatesInCycleCollection(JS::GCCellPtr aThing, const char* aName,
348 : void* aClosure)
349 : {
350 0 : bool* cycleCollectionEnabled = static_cast<bool*>(aClosure);
351 :
352 0 : if (*cycleCollectionEnabled) {
353 0 : return;
354 : }
355 :
356 0 : if (AddToCCKind(aThing.kind()) && JS::GCThingIsMarkedGray(aThing)) {
357 0 : *cycleCollectionEnabled = true;
358 : }
359 : }
360 :
361 : NS_IMETHODIMP
362 0 : JSGCThingParticipant::TraverseNative(void* aPtr,
363 : nsCycleCollectionTraversalCallback& aCb)
364 : {
365 : auto runtime = reinterpret_cast<CycleCollectedJSRuntime*>(
366 : reinterpret_cast<char*>(this) - offsetof(CycleCollectedJSRuntime,
367 0 : mGCThingCycleCollectorGlobal));
368 :
369 0 : JS::GCCellPtr cellPtr(aPtr, JS::GCThingTraceKind(aPtr));
370 0 : runtime->TraverseGCThing(CycleCollectedJSRuntime::TRAVERSE_FULL, cellPtr, aCb);
371 0 : return NS_OK;
372 : }
373 :
374 : // NB: This is only used to initialize the participant in
375 : // CycleCollectedJSRuntime. It should never be used directly.
376 : static JSGCThingParticipant sGCThingCycleCollectorGlobal;
377 :
378 : NS_IMETHODIMP
379 0 : JSZoneParticipant::TraverseNative(void* aPtr,
380 : nsCycleCollectionTraversalCallback& aCb)
381 : {
382 : auto runtime = reinterpret_cast<CycleCollectedJSRuntime*>(
383 : reinterpret_cast<char*>(this) - offsetof(CycleCollectedJSRuntime,
384 0 : mJSZoneCycleCollectorGlobal));
385 :
386 0 : MOZ_ASSERT(!aCb.WantAllTraces());
387 0 : JS::Zone* zone = static_cast<JS::Zone*>(aPtr);
388 :
389 0 : runtime->TraverseZone(zone, aCb);
390 0 : return NS_OK;
391 : }
392 :
393 : struct TraversalTracer : public JS::CallbackTracer
394 : {
395 0 : TraversalTracer(JSRuntime* aRt, nsCycleCollectionTraversalCallback& aCb)
396 0 : : JS::CallbackTracer(aRt, DoNotTraceWeakMaps), mCb(aCb)
397 : {
398 0 : }
399 : void onChild(const JS::GCCellPtr& aThing) override;
400 : nsCycleCollectionTraversalCallback& mCb;
401 : };
402 :
403 : void
404 0 : TraversalTracer::onChild(const JS::GCCellPtr& aThing)
405 : {
406 : // Don't traverse non-gray objects, unless we want all traces.
407 0 : if (!JS::GCThingIsMarkedGray(aThing) && !mCb.WantAllTraces()) {
408 0 : return;
409 : }
410 :
411 : /*
412 : * This function needs to be careful to avoid stack overflow. Normally, when
413 : * AddToCCKind is true, the recursion terminates immediately as we just add
414 : * |thing| to the CC graph. So overflow is only possible when there are long
415 : * or cyclic chains of non-AddToCCKind GC things. Places where this can occur
416 : * use special APIs to handle such chains iteratively.
417 : */
418 0 : if (AddToCCKind(aThing.kind())) {
419 0 : if (MOZ_UNLIKELY(mCb.WantDebugInfo())) {
420 : char buffer[200];
421 0 : getTracingEdgeName(buffer, sizeof(buffer));
422 0 : mCb.NoteNextEdgeName(buffer);
423 : }
424 0 : mCb.NoteJSChild(aThing);
425 0 : } else if (aThing.is<js::Shape>()) {
426 : // The maximum depth of traversal when tracing a Shape is unbounded, due to
427 : // the parent pointers on the shape.
428 0 : JS_TraceShapeCycleCollectorChildren(this, aThing);
429 0 : } else if (aThing.is<js::ObjectGroup>()) {
430 : // The maximum depth of traversal when tracing an ObjectGroup is unbounded,
431 : // due to information attached to the groups which can lead other groups to
432 : // be traced.
433 0 : JS_TraceObjectGroupCycleCollectorChildren(this, aThing);
434 0 : } else if (!aThing.is<JSString>()) {
435 0 : JS::TraceChildren(this, aThing);
436 : }
437 : }
438 :
439 : static void
440 0 : NoteJSChildGrayWrapperShim(void* aData, JS::GCCellPtr aThing)
441 : {
442 0 : TraversalTracer* trc = static_cast<TraversalTracer*>(aData);
443 0 : trc->onChild(aThing);
444 0 : }
445 :
446 : /*
447 : * The cycle collection participant for a Zone is intended to produce the same
448 : * results as if all of the gray GCthings in a zone were merged into a single node,
449 : * except for self-edges. This avoids the overhead of representing all of the GCthings in
450 : * the zone in the cycle collector graph, which should be much faster if many of
451 : * the GCthings in the zone are gray.
452 : *
453 : * Zone merging should not always be used, because it is a conservative
454 : * approximation of the true cycle collector graph that can incorrectly identify some
455 : * garbage objects as being live. For instance, consider two cycles that pass through a
456 : * zone, where one is garbage and the other is live. If we merge the entire
457 : * zone, the cycle collector will think that both are alive.
458 : *
459 : * We don't have to worry about losing track of a garbage cycle, because any such garbage
460 : * cycle incorrectly identified as live must contain at least one C++ to JS edge, and
461 : * XPConnect will always add the C++ object to the CC graph. (This is in contrast to pure
462 : * C++ garbage cycles, which must always be properly identified, because we clear the
463 : * purple buffer during every CC, which may contain the last reference to a garbage
464 : * cycle.)
465 : */
466 :
467 : // NB: This is only used to initialize the participant in
468 : // CycleCollectedJSRuntime. It should never be used directly.
469 : static const JSZoneParticipant sJSZoneCycleCollectorGlobal;
470 :
471 : static
472 21 : void JSObjectsTenuredCb(JSContext* aContext, void* aData)
473 : {
474 21 : static_cast<CycleCollectedJSRuntime*>(aData)->JSObjectsTenured();
475 21 : }
476 :
477 : bool
478 1445 : mozilla::GetBuildId(JS::BuildIdCharVector* aBuildID)
479 : {
480 2890 : nsCOMPtr<nsIPlatformInfo> info = do_GetService("@mozilla.org/xre/app-info;1");
481 1445 : if (!info) {
482 0 : return false;
483 : }
484 :
485 2890 : nsCString buildID;
486 1445 : nsresult rv = info->GetPlatformBuildID(buildID);
487 1445 : NS_ENSURE_SUCCESS(rv, false);
488 :
489 1445 : if (!aBuildID->resize(buildID.Length())) {
490 0 : return false;
491 : }
492 :
493 21675 : for (size_t i = 0; i < buildID.Length(); i++) {
494 20230 : (*aBuildID)[i] = buildID[i];
495 : }
496 :
497 1445 : return true;
498 : }
499 :
500 : static void
501 0 : MozCrashWarningReporter(JSContext*, JSErrorReport*)
502 : {
503 0 : MOZ_CRASH("Why is someone touching JSAPI without an AutoJSAPI?");
504 : }
505 :
506 4 : CycleCollectedJSRuntime::CycleCollectedJSRuntime(JSContext* aCx)
507 : : mGCThingCycleCollectorGlobal(sGCThingCycleCollectorGlobal)
508 : , mJSZoneCycleCollectorGlobal(sJSZoneCycleCollectorGlobal)
509 4 : , mJSRuntime(JS_GetRuntime(aCx))
510 : , mPrevGCSliceCallback(nullptr)
511 : , mPrevGCNurseryCollectionCallback(nullptr)
512 : , mJSHolderMap(256)
513 : , mOutOfMemoryState(OOMState::OK)
514 8 : , mLargeAllocationFailureState(OOMState::OK)
515 : {
516 4 : MOZ_COUNT_CTOR(CycleCollectedJSRuntime);
517 4 : MOZ_ASSERT(aCx);
518 4 : MOZ_ASSERT(mJSRuntime);
519 :
520 4 : if (!JS_AddExtraGCRootsTracer(aCx, TraceBlackJS, this)) {
521 0 : MOZ_CRASH("JS_AddExtraGCRootsTracer failed");
522 : }
523 4 : JS_SetGrayGCRootsTracer(aCx, TraceGrayJS, this);
524 4 : JS_SetGCCallback(aCx, GCCallback, this);
525 4 : mPrevGCSliceCallback = JS::SetGCSliceCallback(aCx, GCSliceCallback);
526 :
527 4 : if (NS_IsMainThread()) {
528 : // We would like to support all threads here, but the way timeline consumers
529 : // are set up currently, you can either add a marker for one specific
530 : // docshell, or for every consumer globally. We would like to add a marker
531 : // for every consumer observing anything on this thread, but that is not
532 : // currently possible. For now, add global markers only when we are on the
533 : // main thread, since the UI for this tracing data only displays data
534 : // relevant to the main-thread.
535 3 : mPrevGCNurseryCollectionCallback = JS::SetGCNurseryCollectionCallback(
536 : aCx, GCNurseryCollectionCallback);
537 : }
538 :
539 4 : JS_SetObjectsTenuredCallback(aCx, JSObjectsTenuredCb, this);
540 4 : JS::SetOutOfMemoryCallback(aCx, OutOfMemoryCallback, this);
541 4 : JS_SetExternalStringSizeofCallback(aCx, SizeofExternalStringCallback);
542 4 : JS::SetBuildIdOp(aCx, GetBuildId);
543 4 : JS::SetWarningReporter(aCx, MozCrashWarningReporter);
544 : #ifdef MOZ_CRASHREPORTER
545 : js::AutoEnterOOMUnsafeRegion::setAnnotateOOMAllocationSizeCallback(
546 4 : CrashReporter::AnnotateOOMAllocationSize);
547 : #endif
548 :
549 : static js::DOMCallbacks DOMcallbacks = {
550 : InstanceClassHasProtoAtDepth
551 : };
552 4 : SetDOMCallbacks(aCx, &DOMcallbacks);
553 4 : js::SetScriptEnvironmentPreparer(aCx, &mEnvironmentPreparer);
554 :
555 4 : JS::dbg::SetDebuggerMallocSizeOf(aCx, moz_malloc_size_of);
556 4 : }
557 :
558 : void
559 0 : CycleCollectedJSRuntime::Shutdown(JSContext* cx)
560 : {
561 0 : JS_RemoveExtraGCRootsTracer(cx, TraceBlackJS, this);
562 0 : JS_RemoveExtraGCRootsTracer(cx, TraceGrayJS, this);
563 0 : }
564 :
565 0 : CycleCollectedJSRuntime::~CycleCollectedJSRuntime()
566 : {
567 0 : MOZ_COUNT_DTOR(CycleCollectedJSRuntime);
568 0 : MOZ_ASSERT(!mDeferredFinalizerTable.Count());
569 0 : }
570 :
571 : void
572 4 : CycleCollectedJSRuntime::AddContext(CycleCollectedJSContext* aContext)
573 : {
574 4 : mContexts.insertBack(aContext);
575 4 : }
576 :
577 : void
578 0 : CycleCollectedJSRuntime::RemoveContext(CycleCollectedJSContext* aContext)
579 : {
580 0 : aContext->removeFrom(mContexts);
581 0 : }
582 :
583 : size_t
584 0 : CycleCollectedJSRuntime::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
585 : {
586 0 : size_t n = 0;
587 :
588 : // We're deliberately not measuring anything hanging off the entries in
589 : // mJSHolders.
590 0 : n += mJSHolders.SizeOfExcludingThis(aMallocSizeOf);
591 0 : n += mJSHolderMap.ShallowSizeOfExcludingThis(aMallocSizeOf);
592 :
593 0 : return n;
594 : }
595 :
596 : void
597 0 : CycleCollectedJSRuntime::UnmarkSkippableJSHolders()
598 : {
599 0 : for (auto iter = mJSHolders.Iter(); !iter.Done(); iter.Next()) {
600 0 : void* holder = iter.Get().mHolder;
601 0 : nsScriptObjectTracer* tracer = iter.Get().mTracer;
602 0 : tracer->CanSkip(holder, true);
603 : }
604 0 : }
605 :
606 : void
607 0 : CycleCollectedJSRuntime::DescribeGCThing(bool aIsMarked, JS::GCCellPtr aThing,
608 : nsCycleCollectionTraversalCallback& aCb) const
609 : {
610 0 : if (!aCb.WantDebugInfo()) {
611 0 : aCb.DescribeGCedNode(aIsMarked, "JS Object");
612 0 : return;
613 : }
614 :
615 : char name[72];
616 0 : uint64_t compartmentAddress = 0;
617 0 : if (aThing.is<JSObject>()) {
618 0 : JSObject* obj = &aThing.as<JSObject>();
619 0 : compartmentAddress = (uint64_t)js::GetObjectCompartment(obj);
620 0 : const js::Class* clasp = js::GetObjectClass(obj);
621 :
622 : // Give the subclass a chance to do something
623 0 : if (DescribeCustomObjects(obj, clasp, name)) {
624 : // Nothing else to do!
625 0 : } else if (js::IsFunctionObject(obj)) {
626 0 : JSFunction* fun = JS_GetObjectFunction(obj);
627 0 : JSString* str = JS_GetFunctionDisplayId(fun);
628 0 : if (str) {
629 0 : JSFlatString* flat = JS_ASSERT_STRING_IS_FLAT(str);
630 0 : nsAutoString chars;
631 0 : AssignJSFlatString(chars, flat);
632 0 : NS_ConvertUTF16toUTF8 fname(chars);
633 0 : SprintfLiteral(name, "JS Object (Function - %s)", fname.get());
634 : } else {
635 0 : SprintfLiteral(name, "JS Object (Function)");
636 : }
637 : } else {
638 0 : SprintfLiteral(name, "JS Object (%s)", clasp->name);
639 : }
640 : } else {
641 0 : SprintfLiteral(name, "JS %s", JS::GCTraceKindToAscii(aThing.kind()));
642 : }
643 :
644 : // Disable printing global for objects while we figure out ObjShrink fallout.
645 0 : aCb.DescribeGCedNode(aIsMarked, name, compartmentAddress);
646 : }
647 :
648 : void
649 0 : CycleCollectedJSRuntime::NoteGCThingJSChildren(JS::GCCellPtr aThing,
650 : nsCycleCollectionTraversalCallback& aCb) const
651 : {
652 0 : TraversalTracer trc(mJSRuntime, aCb);
653 0 : JS::TraceChildren(&trc, aThing);
654 0 : }
655 :
656 : void
657 0 : CycleCollectedJSRuntime::NoteGCThingXPCOMChildren(const js::Class* aClasp,
658 : JSObject* aObj,
659 : nsCycleCollectionTraversalCallback& aCb) const
660 : {
661 0 : MOZ_ASSERT(aClasp);
662 0 : MOZ_ASSERT(aClasp == js::GetObjectClass(aObj));
663 :
664 0 : if (NoteCustomGCThingXPCOMChildren(aClasp, aObj, aCb)) {
665 : // Nothing else to do!
666 0 : return;
667 : }
668 : // XXX This test does seem fragile, we should probably whitelist classes
669 : // that do hold a strong reference, but that might not be possible.
670 0 : else if (aClasp->flags & JSCLASS_HAS_PRIVATE &&
671 0 : aClasp->flags & JSCLASS_PRIVATE_IS_NSISUPPORTS) {
672 0 : NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "js::GetObjectPrivate(obj)");
673 0 : aCb.NoteXPCOMChild(static_cast<nsISupports*>(js::GetObjectPrivate(aObj)));
674 : } else {
675 0 : const DOMJSClass* domClass = GetDOMClass(aObj);
676 0 : if (domClass) {
677 0 : NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "UnwrapDOMObject(obj)");
678 : // It's possible that our object is an unforgeable holder object, in
679 : // which case it doesn't actually have a C++ DOM object associated with
680 : // it. Use UnwrapPossiblyNotInitializedDOMObject, which produces null in
681 : // that case, since NoteXPCOMChild/NoteNativeChild are null-safe.
682 0 : if (domClass->mDOMObjectIsISupports) {
683 0 : aCb.NoteXPCOMChild(UnwrapPossiblyNotInitializedDOMObject<nsISupports>(aObj));
684 0 : } else if (domClass->mParticipant) {
685 0 : aCb.NoteNativeChild(UnwrapPossiblyNotInitializedDOMObject<void>(aObj),
686 0 : domClass->mParticipant);
687 : }
688 : }
689 : }
690 : }
691 :
692 : void
693 0 : CycleCollectedJSRuntime::TraverseGCThing(TraverseSelect aTs, JS::GCCellPtr aThing,
694 : nsCycleCollectionTraversalCallback& aCb)
695 : {
696 0 : bool isMarkedGray = JS::GCThingIsMarkedGray(aThing);
697 :
698 0 : if (aTs == TRAVERSE_FULL) {
699 0 : DescribeGCThing(!isMarkedGray, aThing, aCb);
700 : }
701 :
702 : // If this object is alive, then all of its children are alive. For JS objects,
703 : // the black-gray invariant ensures the children are also marked black. For C++
704 : // objects, the ref count from this object will keep them alive. Thus we don't
705 : // need to trace our children, unless we are debugging using WantAllTraces.
706 0 : if (!isMarkedGray && !aCb.WantAllTraces()) {
707 0 : return;
708 : }
709 :
710 0 : if (aTs == TRAVERSE_FULL) {
711 0 : NoteGCThingJSChildren(aThing, aCb);
712 : }
713 :
714 0 : if (aThing.is<JSObject>()) {
715 0 : JSObject* obj = &aThing.as<JSObject>();
716 0 : NoteGCThingXPCOMChildren(js::GetObjectClass(obj), obj, aCb);
717 : }
718 : }
719 :
720 : struct TraverseObjectShimClosure
721 : {
722 : nsCycleCollectionTraversalCallback& cb;
723 : CycleCollectedJSRuntime* self;
724 : };
725 :
726 : void
727 0 : CycleCollectedJSRuntime::TraverseZone(JS::Zone* aZone,
728 : nsCycleCollectionTraversalCallback& aCb)
729 : {
730 : /*
731 : * We treat the zone as being gray. We handle non-gray GCthings in the
732 : * zone by not reporting their children to the CC. The black-gray invariant
733 : * ensures that any JS children will also be non-gray, and thus don't need to be
734 : * added to the graph. For C++ children, not representing the edge from the
735 : * non-gray JS GCthings to the C++ object will keep the child alive.
736 : *
737 : * We don't allow zone merging in a WantAllTraces CC, because then these
738 : * assumptions don't hold.
739 : */
740 0 : aCb.DescribeGCedNode(false, "JS Zone");
741 :
742 : /*
743 : * Every JS child of everything in the zone is either in the zone
744 : * or is a cross-compartment wrapper. In the former case, we don't need to
745 : * represent these edges in the CC graph because JS objects are not ref counted.
746 : * In the latter case, the JS engine keeps a map of these wrappers, which we
747 : * iterate over. Edges between compartments in the same zone will add
748 : * unnecessary loop edges to the graph (bug 842137).
749 : */
750 0 : TraversalTracer trc(mJSRuntime, aCb);
751 0 : js::VisitGrayWrapperTargets(aZone, NoteJSChildGrayWrapperShim, &trc);
752 :
753 : /*
754 : * To find C++ children of things in the zone, we scan every JS Object in
755 : * the zone. Only JS Objects can have C++ children.
756 : */
757 0 : TraverseObjectShimClosure closure = { aCb, this };
758 0 : js::IterateGrayObjects(aZone, TraverseObjectShim, &closure);
759 0 : }
760 :
761 : /* static */ void
762 0 : CycleCollectedJSRuntime::TraverseObjectShim(void* aData, JS::GCCellPtr aThing)
763 : {
764 : TraverseObjectShimClosure* closure =
765 0 : static_cast<TraverseObjectShimClosure*>(aData);
766 :
767 0 : MOZ_ASSERT(aThing.is<JSObject>());
768 0 : closure->self->TraverseGCThing(CycleCollectedJSRuntime::TRAVERSE_CPP,
769 0 : aThing, closure->cb);
770 0 : }
771 :
772 : void
773 0 : CycleCollectedJSRuntime::TraverseNativeRoots(nsCycleCollectionNoteRootCallback& aCb)
774 : {
775 : // NB: This is here just to preserve the existing XPConnect order. I doubt it
776 : // would hurt to do this after the JS holders.
777 0 : TraverseAdditionalNativeRoots(aCb);
778 :
779 0 : for (auto iter = mJSHolders.Iter(); !iter.Done(); iter.Next()) {
780 0 : void* holder = iter.Get().mHolder;
781 0 : nsScriptObjectTracer* tracer = iter.Get().mTracer;
782 :
783 0 : bool noteRoot = false;
784 0 : if (MOZ_UNLIKELY(aCb.WantAllTraces())) {
785 0 : noteRoot = true;
786 : } else {
787 : tracer->Trace(holder,
788 0 : TraceCallbackFunc(CheckParticipatesInCycleCollection),
789 0 : ¬eRoot);
790 : }
791 :
792 0 : if (noteRoot) {
793 0 : aCb.NoteNativeRoot(holder, tracer);
794 : }
795 : }
796 0 : }
797 :
798 : /* static */ void
799 1 : CycleCollectedJSRuntime::TraceBlackJS(JSTracer* aTracer, void* aData)
800 : {
801 1 : CycleCollectedJSRuntime* self = static_cast<CycleCollectedJSRuntime*>(aData);
802 :
803 1 : self->TraceNativeBlackRoots(aTracer);
804 1 : }
805 :
806 : /* static */ void
807 1 : CycleCollectedJSRuntime::TraceGrayJS(JSTracer* aTracer, void* aData)
808 : {
809 1 : CycleCollectedJSRuntime* self = static_cast<CycleCollectedJSRuntime*>(aData);
810 :
811 : // Mark these roots as gray so the CC can walk them later.
812 1 : self->TraceNativeGrayRoots(aTracer);
813 1 : }
814 :
815 : /* static */ void
816 1 : CycleCollectedJSRuntime::GCCallback(JSContext* aContext,
817 : JSGCStatus aStatus,
818 : void* aData)
819 : {
820 1 : CycleCollectedJSRuntime* self = static_cast<CycleCollectedJSRuntime*>(aData);
821 :
822 1 : MOZ_ASSERT(CycleCollectedJSContext::Get()->Context() == aContext);
823 1 : MOZ_ASSERT(CycleCollectedJSContext::Get()->Runtime() == self);
824 :
825 1 : self->OnGC(aContext, aStatus);
826 1 : }
827 :
828 : /* static */ void
829 7 : CycleCollectedJSRuntime::GCSliceCallback(JSContext* aContext,
830 : JS::GCProgress aProgress,
831 : const JS::GCDescription& aDesc)
832 : {
833 7 : CycleCollectedJSRuntime* self = CycleCollectedJSRuntime::Get();
834 7 : MOZ_ASSERT(CycleCollectedJSContext::Get()->Context() == aContext);
835 :
836 7 : if (profiler_is_active()) {
837 0 : if (aProgress == JS::GC_CYCLE_END) {
838 0 : profiler_add_marker(
839 : "GCMajor",
840 0 : MakeUnique<GCMajorMarkerPayload>(aDesc.startTime(aContext),
841 0 : aDesc.endTime(aContext),
842 0 : aDesc.summaryToJSON(aContext)));
843 0 : } else if (aProgress == JS::GC_SLICE_END) {
844 0 : profiler_add_marker(
845 : "GCSlice",
846 0 : MakeUnique<GCSliceMarkerPayload>(aDesc.lastSliceStart(aContext),
847 0 : aDesc.lastSliceEnd(aContext),
848 0 : aDesc.sliceToJSON(aContext)));
849 : }
850 : }
851 :
852 7 : if (aProgress == JS::GC_CYCLE_END &&
853 0 : JS::dbg::FireOnGarbageCollectionHookRequired(aContext)) {
854 0 : JS::gcreason::Reason reason = aDesc.reason_;
855 : Unused <<
856 0 : NS_WARN_IF(NS_FAILED(DebuggerOnGCRunnable::Enqueue(aContext, aDesc)) &&
857 : reason != JS::gcreason::SHUTDOWN_CC &&
858 : reason != JS::gcreason::DESTROY_RUNTIME &&
859 : reason != JS::gcreason::XPCONNECT_SHUTDOWN);
860 : }
861 :
862 7 : if (self->mPrevGCSliceCallback) {
863 0 : self->mPrevGCSliceCallback(aContext, aProgress, aDesc);
864 : }
865 7 : }
866 :
867 0 : class MinorGCMarker : public TimelineMarker
868 : {
869 : private:
870 : JS::gcreason::Reason mReason;
871 :
872 : public:
873 0 : MinorGCMarker(MarkerTracingType aTracingType,
874 : JS::gcreason::Reason aReason)
875 0 : : TimelineMarker("MinorGC",
876 : aTracingType,
877 : MarkerStackRequest::NO_STACK)
878 0 : , mReason(aReason)
879 : {
880 0 : MOZ_ASSERT(aTracingType == MarkerTracingType::START ||
881 : aTracingType == MarkerTracingType::END);
882 0 : }
883 :
884 0 : MinorGCMarker(JS::GCNurseryProgress aProgress,
885 : JS::gcreason::Reason aReason)
886 0 : : TimelineMarker("MinorGC",
887 : aProgress == JS::GCNurseryProgress::GC_NURSERY_COLLECTION_START
888 : ? MarkerTracingType::START
889 : : MarkerTracingType::END,
890 : MarkerStackRequest::NO_STACK)
891 0 : , mReason(aReason)
892 0 : { }
893 :
894 : virtual void
895 0 : AddDetails(JSContext* aCx,
896 : dom::ProfileTimelineMarker& aMarker) override
897 : {
898 0 : TimelineMarker::AddDetails(aCx, aMarker);
899 :
900 0 : if (GetTracingType() == MarkerTracingType::START) {
901 0 : auto reason = JS::gcreason::ExplainReason(mReason);
902 0 : aMarker.mCauseName.Construct(NS_ConvertUTF8toUTF16(reason));
903 : }
904 0 : }
905 :
906 : virtual UniquePtr<AbstractTimelineMarker>
907 0 : Clone() override
908 : {
909 0 : auto clone = MakeUnique<MinorGCMarker>(GetTracingType(), mReason);
910 0 : clone->SetCustomTime(GetTime());
911 0 : return UniquePtr<AbstractTimelineMarker>(Move(clone));
912 : }
913 : };
914 :
915 : /* static */ void
916 48 : CycleCollectedJSRuntime::GCNurseryCollectionCallback(JSContext* aContext,
917 : JS::GCNurseryProgress aProgress,
918 : JS::gcreason::Reason aReason)
919 : {
920 48 : CycleCollectedJSRuntime* self = CycleCollectedJSRuntime::Get();
921 48 : MOZ_ASSERT(CycleCollectedJSContext::Get()->Context() == aContext);
922 48 : MOZ_ASSERT(NS_IsMainThread());
923 :
924 96 : RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
925 48 : if (timelines && !timelines->IsEmpty()) {
926 : UniquePtr<AbstractTimelineMarker> abstractMarker(
927 0 : MakeUnique<MinorGCMarker>(aProgress, aReason));
928 0 : timelines->AddMarkerForAllObservedDocShells(abstractMarker);
929 : }
930 :
931 48 : if (aProgress == JS::GCNurseryProgress::GC_NURSERY_COLLECTION_START) {
932 24 : self->mLatestNurseryCollectionStart = TimeStamp::Now();
933 48 : } else if ((aProgress == JS::GCNurseryProgress::GC_NURSERY_COLLECTION_END) &&
934 24 : profiler_is_active())
935 : {
936 0 : profiler_add_marker(
937 : "GCMinor",
938 0 : MakeUnique<GCMinorMarkerPayload>(self->mLatestNurseryCollectionStart,
939 0 : TimeStamp::Now(),
940 0 : JS::MinorGcToJSON(aContext)));
941 : }
942 :
943 48 : if (self->mPrevGCNurseryCollectionCallback) {
944 0 : self->mPrevGCNurseryCollectionCallback(aContext, aProgress, aReason);
945 : }
946 48 : }
947 :
948 :
949 : /* static */ void
950 0 : CycleCollectedJSRuntime::OutOfMemoryCallback(JSContext* aContext,
951 : void* aData)
952 : {
953 0 : CycleCollectedJSRuntime* self = static_cast<CycleCollectedJSRuntime*>(aData);
954 :
955 0 : MOZ_ASSERT(CycleCollectedJSContext::Get()->Context() == aContext);
956 0 : MOZ_ASSERT(CycleCollectedJSContext::Get()->Runtime() == self);
957 :
958 0 : self->OnOutOfMemory();
959 0 : }
960 :
961 : /* static */ size_t
962 0 : CycleCollectedJSRuntime::SizeofExternalStringCallback(JSString* aStr,
963 : MallocSizeOf aMallocSizeOf)
964 : {
965 : // We promised the JS engine we would not GC. Enforce that:
966 0 : JS::AutoCheckCannotGC autoCannotGC;
967 :
968 0 : if (!XPCStringConvert::IsDOMString(aStr)) {
969 : // Might be a literal or something we don't understand. Just claim 0.
970 0 : return 0;
971 : }
972 :
973 0 : const char16_t* chars = JS_GetTwoByteExternalStringChars(aStr);
974 0 : const nsStringBuffer* buf = nsStringBuffer::FromData((void*)chars);
975 : // We want sizeof including this, because the entire string buffer is owned by
976 : // the external string. But only report here if we're unshared; if we're
977 : // shared then we don't know who really owns this data.
978 0 : return buf->SizeOfIncludingThisIfUnshared(aMallocSizeOf);
979 : }
980 :
981 3441 : struct JsGcTracer : public TraceCallbacks
982 : {
983 9 : virtual void Trace(JS::Heap<JS::Value>* aPtr, const char* aName,
984 : void* aClosure) const override
985 : {
986 9 : JS::TraceEdge(static_cast<JSTracer*>(aClosure), aPtr, aName);
987 9 : }
988 0 : virtual void Trace(JS::Heap<jsid>* aPtr, const char* aName,
989 : void* aClosure) const override
990 : {
991 0 : JS::TraceEdge(static_cast<JSTracer*>(aClosure), aPtr, aName);
992 0 : }
993 4093 : virtual void Trace(JS::Heap<JSObject*>* aPtr, const char* aName,
994 : void* aClosure) const override
995 : {
996 4093 : JS::TraceEdge(static_cast<JSTracer*>(aClosure), aPtr, aName);
997 4093 : }
998 252 : virtual void Trace(JSObject** aPtr, const char* aName,
999 : void* aClosure) const override
1000 : {
1001 252 : js::UnsafeTraceManuallyBarrieredEdge(static_cast<JSTracer*>(aClosure), aPtr, aName);
1002 252 : }
1003 472 : virtual void Trace(JS::TenuredHeap<JSObject*>* aPtr, const char* aName,
1004 : void* aClosure) const override
1005 : {
1006 472 : JS::TraceEdge(static_cast<JSTracer*>(aClosure), aPtr, aName);
1007 472 : }
1008 0 : virtual void Trace(JS::Heap<JSString*>* aPtr, const char* aName,
1009 : void* aClosure) const override
1010 : {
1011 0 : JS::TraceEdge(static_cast<JSTracer*>(aClosure), aPtr, aName);
1012 0 : }
1013 39 : virtual void Trace(JS::Heap<JSScript*>* aPtr, const char* aName,
1014 : void* aClosure) const override
1015 : {
1016 39 : JS::TraceEdge(static_cast<JSTracer*>(aClosure), aPtr, aName);
1017 39 : }
1018 0 : virtual void Trace(JS::Heap<JSFunction*>* aPtr, const char* aName,
1019 : void* aClosure) const override
1020 : {
1021 0 : JS::TraceEdge(static_cast<JSTracer*>(aClosure), aPtr, aName);
1022 0 : }
1023 : };
1024 :
1025 : void
1026 47 : mozilla::TraceScriptHolder(nsISupports* aHolder, JSTracer* aTracer)
1027 : {
1028 47 : nsXPCOMCycleCollectionParticipant* participant = nullptr;
1029 47 : CallQueryInterface(aHolder, &participant);
1030 47 : participant->Trace(aHolder, JsGcTracer(), aTracer);
1031 47 : }
1032 :
1033 : void
1034 1 : CycleCollectedJSRuntime::TraceNativeGrayRoots(JSTracer* aTracer)
1035 : {
1036 : // NB: This is here just to preserve the existing XPConnect order. I doubt it
1037 : // would hurt to do this after the JS holders.
1038 1 : TraceAdditionalNativeGrayRoots(aTracer);
1039 :
1040 3395 : for (auto iter = mJSHolders.Iter(); !iter.Done(); iter.Next()) {
1041 3394 : void* holder = iter.Get().mHolder;
1042 3394 : nsScriptObjectTracer* tracer = iter.Get().mTracer;
1043 3394 : tracer->Trace(holder, JsGcTracer(), aTracer);
1044 : }
1045 1 : }
1046 :
1047 : void
1048 3461 : CycleCollectedJSRuntime::AddJSHolder(void* aHolder, nsScriptObjectTracer* aTracer)
1049 : {
1050 3461 : JSHolderInfo* info = nullptr;
1051 3461 : if (mJSHolderMap.Get(aHolder, &info)) {
1052 9 : MOZ_ASSERT(info->mHolder == aHolder);
1053 9 : info->mTracer = aTracer;
1054 9 : return;
1055 : }
1056 :
1057 3452 : mJSHolders.InfallibleAppend(JSHolderInfo {aHolder, aTracer});
1058 3452 : mJSHolderMap.Put(aHolder, &mJSHolders.GetLast());
1059 : }
1060 :
1061 267 : struct ClearJSHolder : public TraceCallbacks
1062 : {
1063 1 : virtual void Trace(JS::Heap<JS::Value>* aPtr, const char*, void*) const override
1064 : {
1065 1 : aPtr->setUndefined();
1066 1 : }
1067 :
1068 0 : virtual void Trace(JS::Heap<jsid>* aPtr, const char*, void*) const override
1069 : {
1070 0 : *aPtr = JSID_VOID;
1071 0 : }
1072 :
1073 493 : virtual void Trace(JS::Heap<JSObject*>* aPtr, const char*, void*) const override
1074 : {
1075 493 : *aPtr = nullptr;
1076 493 : }
1077 :
1078 0 : virtual void Trace(JSObject** aPtr, const char* aName,
1079 : void* aClosure) const override
1080 : {
1081 0 : *aPtr = nullptr;
1082 0 : }
1083 :
1084 237 : virtual void Trace(JS::TenuredHeap<JSObject*>* aPtr, const char*, void*) const override
1085 : {
1086 237 : *aPtr = nullptr;
1087 237 : }
1088 :
1089 0 : virtual void Trace(JS::Heap<JSString*>* aPtr, const char*, void*) const override
1090 : {
1091 0 : *aPtr = nullptr;
1092 0 : }
1093 :
1094 0 : virtual void Trace(JS::Heap<JSScript*>* aPtr, const char*, void*) const override
1095 : {
1096 0 : *aPtr = nullptr;
1097 0 : }
1098 :
1099 0 : virtual void Trace(JS::Heap<JSFunction*>* aPtr, const char*, void*) const override
1100 : {
1101 0 : *aPtr = nullptr;
1102 0 : }
1103 : };
1104 :
1105 : void
1106 281 : CycleCollectedJSRuntime::RemoveJSHolder(void* aHolder)
1107 : {
1108 281 : JSHolderInfo* info = nullptr;
1109 281 : if (mJSHolderMap.Get(aHolder, &info)) {
1110 267 : MOZ_ASSERT(info->mHolder == aHolder);
1111 267 : info->mTracer->Trace(aHolder, ClearJSHolder(), nullptr);
1112 :
1113 267 : JSHolderInfo* lastInfo = &mJSHolders.GetLast();
1114 267 : if (info != lastInfo) {
1115 267 : *info = *lastInfo;
1116 267 : mJSHolderMap.Put(info->mHolder, info);
1117 : }
1118 :
1119 267 : mJSHolders.PopLast();
1120 267 : mJSHolderMap.Remove(aHolder);
1121 : }
1122 281 : }
1123 :
1124 : #ifdef DEBUG
1125 : bool
1126 0 : CycleCollectedJSRuntime::IsJSHolder(void* aHolder)
1127 : {
1128 0 : return mJSHolderMap.Get(aHolder, nullptr);
1129 : }
1130 :
1131 : static void
1132 0 : AssertNoGcThing(JS::GCCellPtr aGCThing, const char* aName, void* aClosure)
1133 : {
1134 0 : MOZ_ASSERT(!aGCThing);
1135 0 : }
1136 :
1137 : void
1138 0 : CycleCollectedJSRuntime::AssertNoObjectsToTrace(void* aPossibleJSHolder)
1139 : {
1140 0 : JSHolderInfo* info = nullptr;
1141 0 : if (!mJSHolderMap.Get(aPossibleJSHolder, &info)) {
1142 0 : return;
1143 : }
1144 :
1145 0 : MOZ_ASSERT(info->mHolder == aPossibleJSHolder);
1146 0 : info->mTracer->Trace(aPossibleJSHolder, TraceCallbackFunc(AssertNoGcThing), nullptr);
1147 : }
1148 : #endif
1149 :
1150 : nsCycleCollectionParticipant*
1151 0 : CycleCollectedJSRuntime::GCThingParticipant()
1152 : {
1153 0 : return &mGCThingCycleCollectorGlobal;
1154 : }
1155 :
1156 : nsCycleCollectionParticipant*
1157 0 : CycleCollectedJSRuntime::ZoneParticipant()
1158 : {
1159 0 : return &mJSZoneCycleCollectorGlobal;
1160 : }
1161 :
1162 : nsresult
1163 0 : CycleCollectedJSRuntime::TraverseRoots(nsCycleCollectionNoteRootCallback& aCb)
1164 : {
1165 0 : TraverseNativeRoots(aCb);
1166 :
1167 0 : NoteWeakMapsTracer trc(mJSRuntime, aCb);
1168 0 : js::TraceWeakMaps(&trc);
1169 :
1170 0 : return NS_OK;
1171 : }
1172 :
1173 : bool
1174 0 : CycleCollectedJSRuntime::UsefulToMergeZones() const
1175 : {
1176 0 : return false;
1177 : }
1178 :
1179 : void
1180 0 : CycleCollectedJSRuntime::FixWeakMappingGrayBits() const
1181 : {
1182 0 : MOZ_ASSERT(!JS::IsIncrementalGCInProgress(mJSRuntime),
1183 : "Don't call FixWeakMappingGrayBits during a GC.");
1184 0 : FixWeakMappingGrayBitsTracer fixer(mJSRuntime);
1185 0 : fixer.FixAll();
1186 0 : }
1187 :
1188 : void
1189 0 : CycleCollectedJSRuntime::CheckGrayBits() const
1190 : {
1191 0 : MOZ_ASSERT(!JS::IsIncrementalGCInProgress(mJSRuntime),
1192 : "Don't call CheckGrayBits during a GC.");
1193 :
1194 : #ifndef ANDROID
1195 : // Bug 1346874 - The gray state check is expensive. Android tests are already
1196 : // slow enough that this check can easily push them over the threshold to a
1197 : // timeout.
1198 :
1199 0 : MOZ_ASSERT(js::CheckGrayMarkingState(mJSRuntime));
1200 0 : MOZ_ASSERT(CheckWeakMappingGrayBitsTracer::Check(mJSRuntime));
1201 : #endif
1202 0 : }
1203 :
1204 : bool
1205 0 : CycleCollectedJSRuntime::AreGCGrayBitsValid() const
1206 : {
1207 0 : return js::AreGCGrayBitsValid(mJSRuntime);
1208 : }
1209 :
1210 : void
1211 0 : CycleCollectedJSRuntime::GarbageCollect(uint32_t aReason) const
1212 : {
1213 0 : MOZ_ASSERT(aReason < JS::gcreason::NUM_REASONS);
1214 0 : JS::gcreason::Reason gcreason = static_cast<JS::gcreason::Reason>(aReason);
1215 :
1216 0 : JSContext* cx = CycleCollectedJSContext::Get()->Context();
1217 0 : JS::PrepareForFullGC(cx);
1218 0 : JS::GCForReason(cx, GC_NORMAL, gcreason);
1219 0 : }
1220 :
1221 : void
1222 21 : CycleCollectedJSRuntime::JSObjectsTenured()
1223 : {
1224 82 : for (auto iter = mNurseryObjects.Iter(); !iter.Done(); iter.Next()) {
1225 61 : nsWrapperCache* cache = iter.Get();
1226 61 : JSObject* wrapper = cache->GetWrapperMaybeDead();
1227 61 : MOZ_DIAGNOSTIC_ASSERT(wrapper);
1228 61 : if (!JS::ObjectIsTenured(wrapper)) {
1229 58 : MOZ_ASSERT(!cache->PreservingWrapper());
1230 58 : const JSClass* jsClass = js::GetObjectJSClass(wrapper);
1231 58 : jsClass->doFinalize(nullptr, wrapper);
1232 : }
1233 : }
1234 :
1235 : #ifdef DEBUG
1236 21 : for (auto iter = mPreservedNurseryObjects.Iter(); !iter.Done(); iter.Next()) {
1237 0 : MOZ_ASSERT(JS::ObjectIsTenured(iter.Get().get()));
1238 : }
1239 : #endif
1240 :
1241 21 : mNurseryObjects.Clear();
1242 21 : mPreservedNurseryObjects.Clear();
1243 21 : }
1244 :
1245 : void
1246 77 : CycleCollectedJSRuntime::NurseryWrapperAdded(nsWrapperCache* aCache)
1247 : {
1248 77 : MOZ_ASSERT(aCache);
1249 77 : MOZ_ASSERT(aCache->GetWrapperMaybeDead());
1250 77 : MOZ_ASSERT(!JS::ObjectIsTenured(aCache->GetWrapperMaybeDead()));
1251 77 : mNurseryObjects.InfallibleAppend(aCache);
1252 77 : }
1253 :
1254 : void
1255 0 : CycleCollectedJSRuntime::NurseryWrapperPreserved(JSObject* aWrapper)
1256 : {
1257 0 : mPreservedNurseryObjects.InfallibleAppend(
1258 0 : JS::PersistentRooted<JSObject*>(mJSRuntime, aWrapper));
1259 0 : }
1260 :
1261 : void
1262 58 : CycleCollectedJSRuntime::DeferredFinalize(DeferredFinalizeAppendFunction aAppendFunc,
1263 : DeferredFinalizeFunction aFunc,
1264 : void* aThing)
1265 : {
1266 116 : if (auto entry = mDeferredFinalizerTable.LookupForAdd(aFunc)) {
1267 57 : aAppendFunc(entry.Data(), aThing);
1268 : } else {
1269 : entry.OrInsert(
1270 2 : [aAppendFunc, aThing] () { return aAppendFunc(nullptr, aThing); });
1271 : }
1272 58 : }
1273 :
1274 : void
1275 58 : CycleCollectedJSRuntime::DeferredFinalize(nsISupports* aSupports)
1276 : {
1277 : typedef DeferredFinalizerImpl<nsISupports> Impl;
1278 : DeferredFinalize(Impl::AppendDeferredFinalizePointer, Impl::DeferredFinalize,
1279 58 : aSupports);
1280 58 : }
1281 :
1282 : void
1283 0 : CycleCollectedJSRuntime::DumpJSHeap(FILE* aFile)
1284 : {
1285 0 : JSContext* cx = CycleCollectedJSContext::Get()->Context();
1286 0 : js::DumpHeap(cx, aFile, js::CollectNurseryBeforeDump);
1287 0 : }
1288 :
1289 0 : IncrementalFinalizeRunnable::IncrementalFinalizeRunnable(CycleCollectedJSRuntime* aRt,
1290 0 : DeferredFinalizerTable& aFinalizers)
1291 : : Runnable("IncrementalFinalizeRunnable")
1292 : , mRuntime(aRt)
1293 : , mFinalizeFunctionToRun(0)
1294 0 : , mReleasing(false)
1295 : {
1296 0 : for (auto iter = aFinalizers.Iter(); !iter.Done(); iter.Next()) {
1297 0 : DeferredFinalizeFunction& function = iter.Key();
1298 0 : void*& data = iter.Data();
1299 :
1300 : DeferredFinalizeFunctionHolder* holder =
1301 0 : mDeferredFinalizeFunctions.AppendElement();
1302 0 : holder->run = function;
1303 0 : holder->data = data;
1304 :
1305 0 : iter.Remove();
1306 : }
1307 0 : }
1308 :
1309 0 : IncrementalFinalizeRunnable::~IncrementalFinalizeRunnable()
1310 : {
1311 0 : MOZ_ASSERT(this != mRuntime->mFinalizeRunnable);
1312 0 : }
1313 :
1314 : void
1315 0 : IncrementalFinalizeRunnable::ReleaseNow(bool aLimited)
1316 : {
1317 0 : if (mReleasing) {
1318 0 : NS_WARNING("Re-entering ReleaseNow");
1319 0 : return;
1320 : }
1321 : {
1322 0 : mozilla::AutoRestore<bool> ar(mReleasing);
1323 0 : mReleasing = true;
1324 0 : MOZ_ASSERT(mDeferredFinalizeFunctions.Length() != 0,
1325 : "We should have at least ReleaseSliceNow to run");
1326 0 : MOZ_ASSERT(mFinalizeFunctionToRun < mDeferredFinalizeFunctions.Length(),
1327 : "No more finalizers to run?");
1328 :
1329 0 : TimeDuration sliceTime = TimeDuration::FromMilliseconds(SliceMillis);
1330 0 : TimeStamp started = TimeStamp::Now();
1331 0 : bool timeout = false;
1332 0 : do {
1333 : const DeferredFinalizeFunctionHolder& function =
1334 0 : mDeferredFinalizeFunctions[mFinalizeFunctionToRun];
1335 0 : if (aLimited) {
1336 0 : bool done = false;
1337 0 : while (!timeout && !done) {
1338 : /*
1339 : * We don't want to read the clock too often, so we try to
1340 : * release slices of 100 items.
1341 : */
1342 0 : done = function.run(100, function.data);
1343 0 : timeout = TimeStamp::Now() - started >= sliceTime;
1344 : }
1345 0 : if (done) {
1346 0 : ++mFinalizeFunctionToRun;
1347 : }
1348 0 : if (timeout) {
1349 0 : break;
1350 : }
1351 : } else {
1352 0 : while (!function.run(UINT32_MAX, function.data));
1353 0 : ++mFinalizeFunctionToRun;
1354 : }
1355 0 : } while (mFinalizeFunctionToRun < mDeferredFinalizeFunctions.Length());
1356 : }
1357 :
1358 0 : if (mFinalizeFunctionToRun == mDeferredFinalizeFunctions.Length()) {
1359 0 : MOZ_ASSERT(mRuntime->mFinalizeRunnable == this);
1360 0 : mDeferredFinalizeFunctions.Clear();
1361 : // NB: This may delete this!
1362 0 : mRuntime->mFinalizeRunnable = nullptr;
1363 : }
1364 : }
1365 :
1366 : NS_IMETHODIMP
1367 0 : IncrementalFinalizeRunnable::Run()
1368 : {
1369 0 : if (mRuntime->mFinalizeRunnable != this) {
1370 : /* These items were already processed synchronously in JSGC_END. */
1371 0 : MOZ_ASSERT(!mDeferredFinalizeFunctions.Length());
1372 0 : return NS_OK;
1373 : }
1374 :
1375 0 : TimeStamp start = TimeStamp::Now();
1376 0 : ReleaseNow(true);
1377 :
1378 0 : if (mDeferredFinalizeFunctions.Length()) {
1379 0 : nsresult rv = NS_DispatchToCurrentThread(this);
1380 0 : if (NS_FAILED(rv)) {
1381 0 : ReleaseNow(false);
1382 : }
1383 : }
1384 :
1385 0 : uint32_t duration = (uint32_t)((TimeStamp::Now() - start).ToMilliseconds());
1386 0 : Telemetry::Accumulate(Telemetry::DEFERRED_FINALIZE_ASYNC, duration);
1387 :
1388 0 : return NS_OK;
1389 : }
1390 :
1391 : void
1392 0 : CycleCollectedJSRuntime::FinalizeDeferredThings(CycleCollectedJSContext::DeferredFinalizeType aType)
1393 : {
1394 : /*
1395 : * If the previous GC created a runnable to finalize objects
1396 : * incrementally, and if it hasn't finished yet, finish it now. We
1397 : * don't want these to build up. We also don't want to allow any
1398 : * existing incremental finalize runnables to run after a
1399 : * non-incremental GC, since they are often used to detect leaks.
1400 : */
1401 0 : if (mFinalizeRunnable) {
1402 0 : mFinalizeRunnable->ReleaseNow(false);
1403 0 : if (mFinalizeRunnable) {
1404 : // If we re-entered ReleaseNow, we couldn't delete mFinalizeRunnable and
1405 : // we need to just continue processing it.
1406 0 : return;
1407 : }
1408 : }
1409 :
1410 0 : if (mDeferredFinalizerTable.Count() == 0) {
1411 0 : return;
1412 : }
1413 :
1414 : mFinalizeRunnable = new IncrementalFinalizeRunnable(this,
1415 0 : mDeferredFinalizerTable);
1416 :
1417 : // Everything should be gone now.
1418 0 : MOZ_ASSERT(mDeferredFinalizerTable.Count() == 0);
1419 :
1420 0 : if (aType == CycleCollectedJSContext::FinalizeIncrementally) {
1421 0 : NS_DispatchToCurrentThread(mFinalizeRunnable);
1422 : } else {
1423 0 : mFinalizeRunnable->ReleaseNow(false);
1424 0 : MOZ_ASSERT(!mFinalizeRunnable);
1425 : }
1426 : }
1427 :
1428 : void
1429 0 : CycleCollectedJSRuntime::AnnotateAndSetOutOfMemory(OOMState* aStatePtr,
1430 : OOMState aNewState)
1431 : {
1432 0 : *aStatePtr = aNewState;
1433 : #ifdef MOZ_CRASHREPORTER
1434 0 : CrashReporter::AnnotateCrashReport(aStatePtr == &mOutOfMemoryState
1435 0 : ? NS_LITERAL_CSTRING("JSOutOfMemory")
1436 0 : : NS_LITERAL_CSTRING("JSLargeAllocationFailure"),
1437 : aNewState == OOMState::Reporting
1438 0 : ? NS_LITERAL_CSTRING("Reporting")
1439 : : aNewState == OOMState::Reported
1440 0 : ? NS_LITERAL_CSTRING("Reported")
1441 0 : : NS_LITERAL_CSTRING("Recovered"));
1442 : #endif
1443 0 : }
1444 :
1445 : void
1446 1 : CycleCollectedJSRuntime::OnGC(JSContext* aContext,
1447 : JSGCStatus aStatus)
1448 : {
1449 1 : switch (aStatus) {
1450 : case JSGC_BEGIN:
1451 1 : nsCycleCollector_prepareForGarbageCollection();
1452 1 : mZonesWaitingForGC.Clear();
1453 1 : break;
1454 : case JSGC_END: {
1455 : #ifdef MOZ_CRASHREPORTER
1456 0 : if (mOutOfMemoryState == OOMState::Reported) {
1457 0 : AnnotateAndSetOutOfMemory(&mOutOfMemoryState, OOMState::Recovered);
1458 : }
1459 0 : if (mLargeAllocationFailureState == OOMState::Reported) {
1460 0 : AnnotateAndSetOutOfMemory(&mLargeAllocationFailureState, OOMState::Recovered);
1461 : }
1462 : #endif
1463 :
1464 : // Do any deferred finalization of native objects. Normally we do this
1465 : // incrementally for an incremental GC, and immediately for a
1466 : // non-incremental GC, on the basis that the type of GC reflects how
1467 : // urgently resources should be destroyed. However under some circumstances
1468 : // (such as in js::InternalCallOrConstruct) we can end up running a
1469 : // non-incremental GC when there is a pending exception, and the finalizers
1470 : // are not set up to handle that. In that case, just run them later, after
1471 : // we've returned to the event loop.
1472 0 : bool finalizeIncrementally = JS::WasIncrementalGC(mJSRuntime) || JS_IsExceptionPending(aContext);
1473 0 : FinalizeDeferredThings(finalizeIncrementally
1474 : ? CycleCollectedJSContext::FinalizeIncrementally
1475 0 : : CycleCollectedJSContext::FinalizeNow);
1476 :
1477 0 : break;
1478 : }
1479 : default:
1480 0 : MOZ_CRASH();
1481 : }
1482 :
1483 1 : CustomGCCallback(aStatus);
1484 1 : }
1485 :
1486 : void
1487 0 : CycleCollectedJSRuntime::OnOutOfMemory()
1488 : {
1489 0 : AnnotateAndSetOutOfMemory(&mOutOfMemoryState, OOMState::Reporting);
1490 0 : CustomOutOfMemoryCallback();
1491 0 : AnnotateAndSetOutOfMemory(&mOutOfMemoryState, OOMState::Reported);
1492 0 : }
1493 :
1494 : void
1495 0 : CycleCollectedJSRuntime::SetLargeAllocationFailure(OOMState aNewState)
1496 : {
1497 0 : AnnotateAndSetOutOfMemory(&mLargeAllocationFailureState, aNewState);
1498 0 : }
1499 :
1500 : void
1501 0 : CycleCollectedJSRuntime::PrepareWaitingZonesForGC()
1502 : {
1503 0 : JSContext* cx = CycleCollectedJSContext::Get()->Context();
1504 0 : if (mZonesWaitingForGC.Count() == 0) {
1505 0 : JS::PrepareForFullGC(cx);
1506 : } else {
1507 0 : for (auto iter = mZonesWaitingForGC.Iter(); !iter.Done(); iter.Next()) {
1508 0 : JS::PrepareZoneForGC(iter.Get()->GetKey());
1509 : }
1510 0 : mZonesWaitingForGC.Clear();
1511 : }
1512 0 : }
1513 :
1514 : void
1515 0 : CycleCollectedJSRuntime::EnvironmentPreparer::invoke(JS::HandleObject scope,
1516 : js::ScriptEnvironmentPreparer::Closure& closure)
1517 : {
1518 0 : nsIGlobalObject* global = xpc::NativeGlobal(scope);
1519 :
1520 : // Not much we can do if we simply don't have a usable global here...
1521 0 : NS_ENSURE_TRUE_VOID(global && global->GetGlobalJSObject());
1522 :
1523 0 : AutoEntryScript aes(global, "JS-engine-initiated execution");
1524 :
1525 0 : MOZ_ASSERT(!JS_IsExceptionPending(aes.cx()));
1526 :
1527 0 : DebugOnly<bool> ok = closure(aes.cx());
1528 :
1529 0 : MOZ_ASSERT_IF(ok, !JS_IsExceptionPending(aes.cx()));
1530 :
1531 : // The AutoEntryScript will check for JS_IsExceptionPending on the
1532 : // JSContext and report it as needed as it comes off the stack.
1533 : }
1534 :
1535 : /* static */ CycleCollectedJSRuntime*
1536 3946 : CycleCollectedJSRuntime::Get()
1537 : {
1538 3946 : auto context = CycleCollectedJSContext::Get();
1539 3946 : if (context) {
1540 3946 : return context->Runtime();
1541 : }
1542 0 : return nullptr;
1543 : }
|