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