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 /* Per JSRuntime object */
8 
9 #include "mozilla/ArrayUtils.h"
10 #include "mozilla/AutoRestore.h"
11 #include "mozilla/MemoryReporting.h"
12 #include "mozilla/UniquePtr.h"
13 
14 #include "xpcprivate.h"
15 #include "xpcpublic.h"
16 #include "XPCWrapper.h"
17 #include "XPCJSMemoryReporter.h"
18 #include "XrayWrapper.h"
19 #include "WrapperFactory.h"
20 #include "mozJSComponentLoader.h"
21 #include "nsNetUtil.h"
22 #include "nsContentSecurityUtils.h"
23 
24 #include "nsExceptionHandler.h"
25 #include "nsIMemoryInfoDumper.h"
26 #include "nsIMemoryReporter.h"
27 #include "nsIObserverService.h"
28 #include "mozilla/dom/Document.h"
29 #include "nsIRunnable.h"
30 #include "nsIPlatformInfo.h"
31 #include "nsPIDOMWindow.h"
32 #include "nsPrintfCString.h"
33 #include "nsScriptSecurityManager.h"
34 #include "nsThreadPool.h"
35 #include "nsWindowSizes.h"
36 #include "mozilla/BasePrincipal.h"
37 #include "mozilla/Preferences.h"
38 #include "mozilla/Telemetry.h"
39 #include "mozilla/Services.h"
40 #include "mozilla/dom/ScriptLoader.h"
41 #include "mozilla/dom/ScriptSettings.h"
42 
43 #include "nsContentUtils.h"
44 #include "nsCCUncollectableMarker.h"
45 #include "nsCycleCollectionNoteRootCallback.h"
46 #include "nsCycleCollector.h"
47 #include "jsapi.h"
48 #include "js/BuildId.h"  // JS::BuildIdCharVector, JS::SetProcessBuildIdOp
49 #include "js/experimental/SourceHook.h"  // js::{,Set}SourceHook
50 #include "js/GCAPI.h"
51 #include "js/MemoryFunctions.h"
52 #include "js/MemoryMetrics.h"
53 #include "js/Object.h"  // JS::GetClass
54 #include "js/Stream.h"  // JS::AbortSignalIsAborted, JS::InitPipeToHandling
55 #include "js/SliceBudget.h"
56 #include "js/UbiNode.h"
57 #include "js/UbiNodeUtils.h"
58 #include "js/friend/UsageStatistics.h"  // JS_TELEMETRY_*, JS_SetAccumulateTelemetryCallback
59 #include "js/friend/WindowProxy.h"  // js::SetWindowProxyClass
60 #include "js/friend/XrayJitInfo.h"  // JS::SetXrayJitInfo
61 #include "mozilla/dom/AbortSignalBinding.h"
62 #include "mozilla/dom/GeneratedAtomList.h"
63 #include "mozilla/dom/BindingUtils.h"
64 #include "mozilla/dom/Element.h"
65 #include "mozilla/dom/WindowBinding.h"
66 #include "mozilla/Atomics.h"
67 #include "mozilla/Attributes.h"
68 #include "mozilla/ProcessHangMonitor.h"
69 #include "mozilla/ProfilerLabels.h"
70 #include "mozilla/Sprintf.h"
71 #include "mozilla/UniquePtrExtensions.h"
72 #include "mozilla/Unused.h"
73 #include "AccessCheck.h"
74 #include "nsGlobalWindow.h"
75 #include "nsAboutProtocolUtils.h"
76 
77 #include "NodeUbiReporting.h"
78 #include "nsIInputStream.h"
79 #include "nsJSPrincipals.h"
80 
81 #ifdef XP_WIN
82 #  include <windows.h>
83 #endif
84 
85 using namespace mozilla;
86 using namespace xpc;
87 using namespace JS;
88 using mozilla::dom::PerThreadAtomCache;
89 
90 /***************************************************************************/
91 
92 const char* const XPCJSRuntime::mStrings[] = {
93     "constructor",      // IDX_CONSTRUCTOR
94     "toString",         // IDX_TO_STRING
95     "toSource",         // IDX_TO_SOURCE
96     "value",            // IDX_VALUE
97     "QueryInterface",   // IDX_QUERY_INTERFACE
98     "Components",       // IDX_COMPONENTS
99     "Cc",               // IDX_CC
100     "Ci",               // IDX_CI
101     "Cr",               // IDX_CR
102     "Cu",               // IDX_CU
103     "wrappedJSObject",  // IDX_WRAPPED_JSOBJECT
104     "prototype",        // IDX_PROTOTYPE
105     "eval",             // IDX_EVAL
106     "controllers",      // IDX_CONTROLLERS
107     "Controllers",      // IDX_CONTROLLERS_CLASS
108     "length",           // IDX_LENGTH
109     "name",             // IDX_NAME
110     "undefined",        // IDX_UNDEFINED
111     "",                 // IDX_EMPTYSTRING
112     "fileName",         // IDX_FILENAME
113     "lineNumber",       // IDX_LINENUMBER
114     "columnNumber",     // IDX_COLUMNNUMBER
115     "stack",            // IDX_STACK
116     "message",          // IDX_MESSAGE
117     "cause",            // IDX_CAUSE
118     "errors",           // IDX_ERRORS
119     "lastIndex",        // IDX_LASTINDEX
120     "then",             // IDX_THEN
121     "isInstance",       // IDX_ISINSTANCE
122     "Infinity",         // IDX_INFINITY
123     "NaN",              // IDX_NAN
124     "classId",          // IDX_CLASS_ID
125     "interfaceId",      // IDX_INTERFACE_ID
126     "initializer",      // IDX_INITIALIZER
127     "print",            // IDX_PRINT
128 };
129 
130 /***************************************************************************/
131 
132 // *Some* NativeSets are referenced from mClassInfo2NativeSetMap.
133 // *All* NativeSets are referenced from mNativeSetMap.
134 // So, in mClassInfo2NativeSetMap we just clear references to the unmarked.
135 // In mNativeSetMap we clear the references to the unmarked *and* delete them.
136 
137 class AsyncFreeSnowWhite : public Runnable {
138  public:
Run()139   NS_IMETHOD Run() override {
140     AUTO_PROFILER_LABEL("AsyncFreeSnowWhite::Run", GCCC_FreeSnowWhite);
141 
142     TimeStamp start = TimeStamp::Now();
143     // 2 ms budget, given that kICCSliceBudget is only 3 ms
144     js::SliceBudget budget = js::SliceBudget(js::TimeBudget(2));
145     bool hadSnowWhiteObjects =
146         nsCycleCollector_doDeferredDeletionWithBudget(budget);
147     Telemetry::Accumulate(
148         Telemetry::CYCLE_COLLECTOR_ASYNC_SNOW_WHITE_FREEING,
149         uint32_t((TimeStamp::Now() - start).ToMilliseconds()));
150     if (hadSnowWhiteObjects && !mContinuation) {
151       mContinuation = true;
152       if (NS_FAILED(Dispatch())) {
153         mActive = false;
154       }
155     } else {
156       mActive = false;
157     }
158     return NS_OK;
159   }
160 
Dispatch()161   nsresult Dispatch() {
162     nsCOMPtr<nsIRunnable> self(this);
163     return NS_DispatchToCurrentThreadQueue(self.forget(), 500,
164                                            EventQueuePriority::Idle);
165   }
166 
Start(bool aContinuation=false,bool aPurge=false)167   void Start(bool aContinuation = false, bool aPurge = false) {
168     if (mContinuation) {
169       mContinuation = aContinuation;
170     }
171     mPurge = aPurge;
172     if (!mActive && NS_SUCCEEDED(Dispatch())) {
173       mActive = true;
174     }
175   }
176 
AsyncFreeSnowWhite()177   AsyncFreeSnowWhite()
178       : Runnable("AsyncFreeSnowWhite"),
179         mContinuation(false),
180         mActive(false),
181         mPurge(false) {}
182 
183  public:
184   bool mContinuation;
185   bool mActive;
186   bool mPurge;
187 };
188 
189 namespace xpc {
190 
CompartmentPrivate(JS::Compartment * c,mozilla::UniquePtr<XPCWrappedNativeScope> scope,mozilla::BasePrincipal * origin,const SiteIdentifier & site)191 CompartmentPrivate::CompartmentPrivate(
192     JS::Compartment* c, mozilla::UniquePtr<XPCWrappedNativeScope> scope,
193     mozilla::BasePrincipal* origin, const SiteIdentifier& site)
194     : originInfo(origin, site),
195       wantXrays(false),
196       allowWaivers(true),
197       isWebExtensionContentScript(false),
198       isUAWidgetCompartment(false),
199       hasExclusiveExpandos(false),
200       wasShutdown(false),
201       mWrappedJSMap(mozilla::MakeUnique<JSObject2WrappedJSMap>()),
202       mScope(std::move(scope)) {
203   MOZ_COUNT_CTOR(xpc::CompartmentPrivate);
204 }
205 
~CompartmentPrivate()206 CompartmentPrivate::~CompartmentPrivate() {
207   MOZ_COUNT_DTOR(xpc::CompartmentPrivate);
208 }
209 
SystemIsBeingShutDown()210 void CompartmentPrivate::SystemIsBeingShutDown() {
211   // We may call this multiple times when the compartment contains more than one
212   // realm.
213   if (!wasShutdown) {
214     mWrappedJSMap->ShutdownMarker();
215     wasShutdown = true;
216   }
217 }
218 
RealmPrivate(JS::Realm * realm)219 RealmPrivate::RealmPrivate(JS::Realm* realm) : scriptability(realm) {
220   mozilla::PodArrayZero(wrapperDenialWarnings);
221 }
222 
223 /* static */
Init(HandleObject aGlobal,const SiteIdentifier & aSite)224 void RealmPrivate::Init(HandleObject aGlobal, const SiteIdentifier& aSite) {
225   MOZ_ASSERT(aGlobal);
226   DebugOnly<const JSClass*> clasp = JS::GetClass(aGlobal);
227   MOZ_ASSERT(clasp->flags &
228                  (JSCLASS_PRIVATE_IS_NSISUPPORTS | JSCLASS_HAS_PRIVATE) ||
229              dom::IsDOMClass(clasp));
230 
231   Realm* realm = GetObjectRealmOrNull(aGlobal);
232 
233   // Create the realm private.
234   RealmPrivate* realmPriv = new RealmPrivate(realm);
235   MOZ_ASSERT(!GetRealmPrivate(realm));
236   SetRealmPrivate(realm, realmPriv);
237 
238   nsIPrincipal* principal = GetRealmPrincipal(realm);
239   Compartment* c = JS::GetCompartment(aGlobal);
240 
241   // Create the compartment private if needed.
242   if (CompartmentPrivate* priv = CompartmentPrivate::Get(c)) {
243     MOZ_ASSERT(priv->originInfo.IsSameOrigin(principal));
244   } else {
245     auto scope = mozilla::MakeUnique<XPCWrappedNativeScope>(c, aGlobal);
246     priv = new CompartmentPrivate(c, std::move(scope),
247                                   BasePrincipal::Cast(principal), aSite);
248     JS_SetCompartmentPrivate(c, priv);
249   }
250 }
251 
TryParseLocationURICandidate(const nsACString & uristr,RealmPrivate::LocationHint aLocationHint,nsIURI ** aURI)252 static bool TryParseLocationURICandidate(
253     const nsACString& uristr, RealmPrivate::LocationHint aLocationHint,
254     nsIURI** aURI) {
255   static constexpr auto kGRE = "resource://gre/"_ns;
256   static constexpr auto kToolkit = "chrome://global/"_ns;
257   static constexpr auto kBrowser = "chrome://browser/"_ns;
258 
259   if (aLocationHint == RealmPrivate::LocationHintAddon) {
260     // Blacklist some known locations which are clearly not add-on related.
261     if (StringBeginsWith(uristr, kGRE) || StringBeginsWith(uristr, kToolkit) ||
262         StringBeginsWith(uristr, kBrowser)) {
263       return false;
264     }
265 
266     // -- GROSS HACK ALERT --
267     // The Yandex Elements 8.10.2 extension implements its own "xb://" URL
268     // scheme. If we call NS_NewURI() on an "xb://..." URL, we'll end up
269     // calling into the extension's own JS-implemented nsIProtocolHandler
270     // object, which we can't allow while we're iterating over the JS heap.
271     // So just skip any such URL.
272     // -- GROSS HACK ALERT --
273     if (StringBeginsWith(uristr, "xb"_ns)) {
274       return false;
275     }
276   }
277 
278   nsCOMPtr<nsIURI> uri;
279   if (NS_FAILED(NS_NewURI(getter_AddRefs(uri), uristr))) {
280     return false;
281   }
282 
283   nsAutoCString scheme;
284   if (NS_FAILED(uri->GetScheme(scheme))) {
285     return false;
286   }
287 
288   // Cannot really map data: and blob:.
289   // Also, data: URIs are pretty memory hungry, which is kinda bad
290   // for memory reporter use.
291   if (scheme.EqualsLiteral("data") || scheme.EqualsLiteral("blob")) {
292     return false;
293   }
294 
295   uri.forget(aURI);
296   return true;
297 }
298 
TryParseLocationURI(RealmPrivate::LocationHint aLocationHint,nsIURI ** aURI)299 bool RealmPrivate::TryParseLocationURI(RealmPrivate::LocationHint aLocationHint,
300                                        nsIURI** aURI) {
301   if (!aURI) {
302     return false;
303   }
304 
305   // Need to parse the URI.
306   if (location.IsEmpty()) {
307     return false;
308   }
309 
310   // Handle Sandbox location strings.
311   // A sandbox string looks like this, for anonymous sandboxes, and builds
312   // where Sandbox location tagging is enabled:
313   //
314   // <sandboxName> (from: <js-stack-frame-filename>:<lineno>)
315   //
316   // where <sandboxName> is user-provided via Cu.Sandbox()
317   // and <js-stack-frame-filename> and <lineno> is the stack frame location
318   // from where Cu.Sandbox was called.
319   //
320   // Otherwise, it is simply the caller-provided name, which is usually a URI.
321   //
322   // <js-stack-frame-filename> furthermore is "free form", often using a
323   // "uri -> uri -> ..." chain. The following code will and must handle this
324   // common case.
325   //
326   // It should be noted that other parts of the code may already rely on the
327   // "format" of these strings.
328 
329   static const nsDependentCString from("(from: ");
330   static const nsDependentCString arrow(" -> ");
331   static const size_t fromLength = from.Length();
332   static const size_t arrowLength = arrow.Length();
333 
334   // See: XPCComponents.cpp#AssembleSandboxMemoryReporterName
335   int32_t idx = location.Find(from);
336   if (idx < 0) {
337     return TryParseLocationURICandidate(location, aLocationHint, aURI);
338   }
339 
340   // When parsing we're looking for the right-most URI. This URI may be in
341   // <sandboxName>, so we try this first.
342   if (TryParseLocationURICandidate(Substring(location, 0, idx), aLocationHint,
343                                    aURI)) {
344     return true;
345   }
346 
347   // Not in <sandboxName> so we need to inspect <js-stack-frame-filename> and
348   // the chain that is potentially contained within and grab the rightmost
349   // item that is actually a URI.
350 
351   // First, hack off the :<lineno>) part as well
352   int32_t ridx = location.RFind(":"_ns);
353   nsAutoCString chain(
354       Substring(location, idx + fromLength, ridx - idx - fromLength));
355 
356   // Loop over the "->" chain. This loop also works for non-chains, or more
357   // correctly chains with only one item.
358   for (;;) {
359     idx = chain.RFind(arrow);
360     if (idx < 0) {
361       // This is the last chain item. Try to parse what is left.
362       return TryParseLocationURICandidate(chain, aLocationHint, aURI);
363     }
364 
365     // Try to parse current chain item
366     if (TryParseLocationURICandidate(Substring(chain, idx + arrowLength),
367                                      aLocationHint, aURI)) {
368       return true;
369     }
370 
371     // Current chain item couldn't be parsed.
372     // Strip current item and continue.
373     chain = Substring(chain, 0, idx);
374   }
375 
376   MOZ_CRASH("Chain parser loop does not terminate");
377 }
378 
PrincipalImmuneToScriptPolicy(nsIPrincipal * aPrincipal)379 static bool PrincipalImmuneToScriptPolicy(nsIPrincipal* aPrincipal) {
380   // System principal gets a free pass.
381   if (aPrincipal->IsSystemPrincipal()) {
382     return true;
383   }
384 
385   auto* principal = BasePrincipal::Cast(aPrincipal);
386 
387   // ExpandedPrincipal gets a free pass.
388   if (principal->Is<ExpandedPrincipal>()) {
389     return true;
390   }
391 
392   // WebExtension principals get a free pass.
393   if (principal->AddonPolicy()) {
394     return true;
395   }
396 
397   // pdf.js is a special-case too.
398   if (nsContentUtils::IsPDFJS(principal)) {
399     return true;
400   }
401 
402   // Check whether our URI is an "about:" URI that allows scripts.  If it is,
403   // we need to allow JS to run.
404   if (aPrincipal->SchemeIs("about")) {
405     uint32_t flags;
406     nsresult rv = aPrincipal->GetAboutModuleFlags(&flags);
407     if (NS_SUCCEEDED(rv) && (flags & nsIAboutModule::ALLOW_SCRIPT)) {
408       return true;
409     }
410   }
411 
412   return false;
413 }
414 
RegisterStackFrame(JSStackFrameBase * aFrame)415 void RealmPrivate::RegisterStackFrame(JSStackFrameBase* aFrame) {
416   mJSStackFrames.PutEntry(aFrame);
417 }
418 
UnregisterStackFrame(JSStackFrameBase * aFrame)419 void RealmPrivate::UnregisterStackFrame(JSStackFrameBase* aFrame) {
420   mJSStackFrames.RemoveEntry(aFrame);
421 }
422 
NukeJSStackFrames()423 void RealmPrivate::NukeJSStackFrames() {
424   for (const auto& key : mJSStackFrames.Keys()) {
425     key->Clear();
426   }
427 
428   mJSStackFrames.Clear();
429 }
430 
RegisterJSStackFrame(JS::Realm * aRealm,JSStackFrameBase * aStackFrame)431 void RegisterJSStackFrame(JS::Realm* aRealm, JSStackFrameBase* aStackFrame) {
432   RealmPrivate* realmPrivate = RealmPrivate::Get(aRealm);
433   if (!realmPrivate) {
434     return;
435   }
436 
437   realmPrivate->RegisterStackFrame(aStackFrame);
438 }
439 
UnregisterJSStackFrame(JS::Realm * aRealm,JSStackFrameBase * aStackFrame)440 void UnregisterJSStackFrame(JS::Realm* aRealm, JSStackFrameBase* aStackFrame) {
441   RealmPrivate* realmPrivate = RealmPrivate::Get(aRealm);
442   if (!realmPrivate) {
443     return;
444   }
445 
446   realmPrivate->UnregisterStackFrame(aStackFrame);
447 }
448 
NukeJSStackFrames(JS::Realm * aRealm)449 void NukeJSStackFrames(JS::Realm* aRealm) {
450   RealmPrivate* realmPrivate = RealmPrivate::Get(aRealm);
451   if (!realmPrivate) {
452     return;
453   }
454 
455   realmPrivate->NukeJSStackFrames();
456 }
457 
Scriptability(JS::Realm * realm)458 Scriptability::Scriptability(JS::Realm* realm)
459     : mScriptBlocks(0),
460       mWindowAllowsScript(true),
461       mScriptBlockedByPolicy(false) {
462   nsIPrincipal* prin = nsJSPrincipals::get(JS::GetRealmPrincipals(realm));
463 
464   mImmuneToScriptPolicy = PrincipalImmuneToScriptPolicy(prin);
465   if (mImmuneToScriptPolicy) {
466     return;
467   }
468   // If we're not immune, we should have a real principal with a URI.
469   // Check the principal against the new-style domain policy.
470   bool policyAllows;
471   nsresult rv = prin->GetIsScriptAllowedByPolicy(&policyAllows);
472   if (NS_SUCCEEDED(rv)) {
473     mScriptBlockedByPolicy = !policyAllows;
474     return;
475   }
476   // Something went wrong - be safe and block script.
477   mScriptBlockedByPolicy = true;
478 }
479 
Allowed()480 bool Scriptability::Allowed() {
481   return mWindowAllowsScript && !mScriptBlockedByPolicy && mScriptBlocks == 0;
482 }
483 
IsImmuneToScriptPolicy()484 bool Scriptability::IsImmuneToScriptPolicy() { return mImmuneToScriptPolicy; }
485 
Block()486 void Scriptability::Block() { ++mScriptBlocks; }
487 
Unblock()488 void Scriptability::Unblock() {
489   MOZ_ASSERT(mScriptBlocks > 0);
490   --mScriptBlocks;
491 }
492 
SetWindowAllowsScript(bool aAllowed)493 void Scriptability::SetWindowAllowsScript(bool aAllowed) {
494   mWindowAllowsScript = aAllowed || mImmuneToScriptPolicy;
495 }
496 
497 /* static */
Get(JSObject * aScope)498 Scriptability& Scriptability::Get(JSObject* aScope) {
499   return RealmPrivate::Get(aScope)->scriptability;
500 }
501 
IsUAWidgetCompartment(JS::Compartment * compartment)502 bool IsUAWidgetCompartment(JS::Compartment* compartment) {
503   // We always eagerly create compartment privates for UA Widget compartments.
504   CompartmentPrivate* priv = CompartmentPrivate::Get(compartment);
505   return priv && priv->isUAWidgetCompartment;
506 }
507 
IsUAWidgetScope(JS::Realm * realm)508 bool IsUAWidgetScope(JS::Realm* realm) {
509   return IsUAWidgetCompartment(JS::GetCompartmentForRealm(realm));
510 }
511 
IsInUAWidgetScope(JSObject * obj)512 bool IsInUAWidgetScope(JSObject* obj) {
513   return IsUAWidgetCompartment(JS::GetCompartment(obj));
514 }
515 
MightBeWebContent() const516 bool CompartmentOriginInfo::MightBeWebContent() const {
517   // Compartments with principals that are either the system principal or an
518   // expanded principal are definitely not web content.
519   return !nsContentUtils::IsSystemOrExpandedPrincipal(mOrigin);
520 }
521 
MightBeWebContentCompartment(JS::Compartment * compartment)522 bool MightBeWebContentCompartment(JS::Compartment* compartment) {
523   if (CompartmentPrivate* priv = CompartmentPrivate::Get(compartment)) {
524     return priv->originInfo.MightBeWebContent();
525   }
526 
527   // No CompartmentPrivate; try IsSystemCompartment.
528   return !js::IsSystemCompartment(compartment);
529 }
530 
IsSameOrigin(nsIPrincipal * aOther) const531 bool CompartmentOriginInfo::IsSameOrigin(nsIPrincipal* aOther) const {
532   return mOrigin->FastEquals(aOther);
533 }
534 
535 /* static */
Subsumes(JS::Compartment * aCompA,JS::Compartment * aCompB)536 bool CompartmentOriginInfo::Subsumes(JS::Compartment* aCompA,
537                                      JS::Compartment* aCompB) {
538   CompartmentPrivate* apriv = CompartmentPrivate::Get(aCompA);
539   CompartmentPrivate* bpriv = CompartmentPrivate::Get(aCompB);
540   MOZ_ASSERT(apriv);
541   MOZ_ASSERT(bpriv);
542   return apriv->originInfo.mOrigin->FastSubsumes(bpriv->originInfo.mOrigin);
543 }
544 
545 /* static */
SubsumesIgnoringFPD(JS::Compartment * aCompA,JS::Compartment * aCompB)546 bool CompartmentOriginInfo::SubsumesIgnoringFPD(JS::Compartment* aCompA,
547                                                 JS::Compartment* aCompB) {
548   CompartmentPrivate* apriv = CompartmentPrivate::Get(aCompA);
549   CompartmentPrivate* bpriv = CompartmentPrivate::Get(aCompB);
550   MOZ_ASSERT(apriv);
551   MOZ_ASSERT(bpriv);
552   return apriv->originInfo.mOrigin->FastSubsumesIgnoringFPD(
553       bpriv->originInfo.mOrigin);
554 }
555 
SetCompartmentChangedDocumentDomain(JS::Compartment * compartment)556 void SetCompartmentChangedDocumentDomain(JS::Compartment* compartment) {
557   // Note: we call this for all compartments that contain realms with a
558   // particular principal. Not all of these compartments have a
559   // CompartmentPrivate (for instance the temporary compartment/realm
560   // created by the JS engine for off-thread parsing).
561   if (CompartmentPrivate* priv = CompartmentPrivate::Get(compartment)) {
562     priv->originInfo.SetChangedDocumentDomain();
563   }
564 }
565 
UnprivilegedJunkScope()566 JSObject* UnprivilegedJunkScope() {
567   return XPCJSRuntime::Get()->UnprivilegedJunkScope();
568 }
569 
UnprivilegedJunkScope(const fallible_t &)570 JSObject* UnprivilegedJunkScope(const fallible_t&) {
571   return XPCJSRuntime::Get()->UnprivilegedJunkScope(fallible);
572 }
573 
IsUnprivilegedJunkScope(JSObject * obj)574 bool IsUnprivilegedJunkScope(JSObject* obj) {
575   return XPCJSRuntime::Get()->IsUnprivilegedJunkScope(obj);
576 }
577 
NACScope(JSObject * global)578 JSObject* NACScope(JSObject* global) {
579   // If we're a chrome global, just use ourselves.
580   if (AccessCheck::isChrome(global)) {
581     return global;
582   }
583 
584   JSObject* scope = UnprivilegedJunkScope();
585   JS::ExposeObjectToActiveJS(scope);
586   return scope;
587 }
588 
PrivilegedJunkScope()589 JSObject* PrivilegedJunkScope() { return XPCJSRuntime::Get()->LoaderGlobal(); }
590 
CompilationScope()591 JSObject* CompilationScope() { return XPCJSRuntime::Get()->LoaderGlobal(); }
592 
WindowOrNull(JSObject * aObj)593 nsGlobalWindowInner* WindowOrNull(JSObject* aObj) {
594   MOZ_ASSERT(aObj);
595   MOZ_ASSERT(!js::IsWrapper(aObj));
596 
597   nsGlobalWindowInner* win = nullptr;
598   UNWRAP_NON_WRAPPER_OBJECT(Window, aObj, win);
599   return win;
600 }
601 
WindowGlobalOrNull(JSObject * aObj)602 nsGlobalWindowInner* WindowGlobalOrNull(JSObject* aObj) {
603   MOZ_ASSERT(aObj);
604   JSObject* glob = JS::GetNonCCWObjectGlobal(aObj);
605 
606   return WindowOrNull(glob);
607 }
608 
SandboxWindowOrNull(JSObject * aObj,JSContext * aCx)609 nsGlobalWindowInner* SandboxWindowOrNull(JSObject* aObj, JSContext* aCx) {
610   MOZ_ASSERT(aObj);
611 
612   if (!IsSandbox(aObj)) {
613     return nullptr;
614   }
615 
616   // Sandbox can't be a Proxy so it must have a static prototype.
617   JSObject* proto = GetStaticPrototype(aObj);
618   if (!proto || !IsSandboxPrototypeProxy(proto)) {
619     return nullptr;
620   }
621 
622   proto = js::CheckedUnwrapDynamic(proto, aCx, /* stopAtWindowProxy = */ false);
623   if (!proto) {
624     return nullptr;
625   }
626   return WindowOrNull(proto);
627 }
628 
CurrentWindowOrNull(JSContext * cx)629 nsGlobalWindowInner* CurrentWindowOrNull(JSContext* cx) {
630   JSObject* glob = JS::CurrentGlobalOrNull(cx);
631   return glob ? WindowOrNull(glob) : nullptr;
632 }
633 
634 // Nukes all wrappers into or out of the given realm, and prevents new
635 // wrappers from being created. Additionally marks the realm as
636 // unscriptable after wrappers have been nuked.
637 //
638 // Note: This should *only* be called for browser or extension realms.
639 // Wrappers between web compartments must never be cut in web-observable
640 // ways.
NukeAllWrappersForRealm(JSContext * cx,JS::Realm * realm,js::NukeReferencesToWindow nukeReferencesToWindow)641 void NukeAllWrappersForRealm(
642     JSContext* cx, JS::Realm* realm,
643     js::NukeReferencesToWindow nukeReferencesToWindow) {
644   // We do the following:
645   // * Nuke all wrappers into the realm.
646   // * Nuke all wrappers out of the realm's compartment, once we have nuked all
647   //   realms in it.
648   js::NukeCrossCompartmentWrappers(cx, js::AllCompartments(), realm,
649                                    nukeReferencesToWindow,
650                                    js::NukeAllReferences);
651 
652   // Mark the realm as unscriptable.
653   xpc::RealmPrivate::Get(realm)->scriptability.Block();
654 }
655 
656 }  // namespace xpc
657 
CompartmentDestroyedCallback(JSFreeOp * fop,JS::Compartment * compartment)658 static void CompartmentDestroyedCallback(JSFreeOp* fop,
659                                          JS::Compartment* compartment) {
660   // NB - This callback may be called in JS_DestroyContext, which happens
661   // after the XPCJSRuntime has been torn down.
662 
663   // Get the current compartment private into a UniquePtr (which will do the
664   // cleanup for us), and null out the private (which may already be null).
665   mozilla::UniquePtr<CompartmentPrivate> priv(
666       CompartmentPrivate::Get(compartment));
667   JS_SetCompartmentPrivate(compartment, nullptr);
668 }
669 
CompartmentSizeOfIncludingThisCallback(MallocSizeOf mallocSizeOf,JS::Compartment * compartment)670 static size_t CompartmentSizeOfIncludingThisCallback(
671     MallocSizeOf mallocSizeOf, JS::Compartment* compartment) {
672   CompartmentPrivate* priv = CompartmentPrivate::Get(compartment);
673   return priv ? priv->SizeOfIncludingThis(mallocSizeOf) : 0;
674 }
675 
676 /*
677  * Return true if there exists a non-system inner window which is a current
678  * inner window and whose reflector is gray.  We don't merge system
679  * compartments, so we don't use them to trigger merging CCs.
680  */
UsefulToMergeZones() const681 bool XPCJSRuntime::UsefulToMergeZones() const {
682   MOZ_ASSERT(NS_IsMainThread());
683 
684   // Turns out, actually making this return true often enough makes Windows
685   // mochitest-gl OOM a lot.  Need to figure out what's going on there; see
686   // bug 1277036.
687 
688   return false;
689 }
690 
TraceNativeBlackRoots(JSTracer * trc)691 void XPCJSRuntime::TraceNativeBlackRoots(JSTracer* trc) {
692   if (CycleCollectedJSContext* ccx = GetContext()) {
693     const auto* cx = static_cast<const XPCJSContext*>(ccx);
694     if (AutoMarkingPtr* roots = cx->mAutoRoots) {
695       roots->TraceJSAll(trc);
696     }
697   }
698 
699   dom::TraceBlackJS(trc, nsIXPConnect::XPConnect()->GetIsShuttingDown());
700 }
701 
TraceAdditionalNativeGrayRoots(JSTracer * trc)702 void XPCJSRuntime::TraceAdditionalNativeGrayRoots(JSTracer* trc) {
703   XPCWrappedNativeScope::TraceWrappedNativesInAllScopes(this, trc);
704 
705   for (XPCRootSetElem* e = mVariantRoots; e; e = e->GetNextRoot()) {
706     static_cast<XPCTraceableVariant*>(e)->TraceJS(trc);
707   }
708 
709   for (XPCRootSetElem* e = mWrappedJSRoots; e; e = e->GetNextRoot()) {
710     static_cast<nsXPCWrappedJS*>(e)->TraceJS(trc);
711   }
712 }
713 
TraverseAdditionalNativeRoots(nsCycleCollectionNoteRootCallback & cb)714 void XPCJSRuntime::TraverseAdditionalNativeRoots(
715     nsCycleCollectionNoteRootCallback& cb) {
716   XPCWrappedNativeScope::SuspectAllWrappers(cb);
717 
718   for (XPCRootSetElem* e = mVariantRoots; e; e = e->GetNextRoot()) {
719     XPCTraceableVariant* v = static_cast<XPCTraceableVariant*>(e);
720     cb.NoteXPCOMRoot(
721         v,
722         XPCTraceableVariant::NS_CYCLE_COLLECTION_INNERCLASS::GetParticipant());
723   }
724 
725   for (XPCRootSetElem* e = mWrappedJSRoots; e; e = e->GetNextRoot()) {
726     cb.NoteXPCOMRoot(
727         ToSupports(static_cast<nsXPCWrappedJS*>(e)),
728         nsXPCWrappedJS::NS_CYCLE_COLLECTION_INNERCLASS::GetParticipant());
729   }
730 }
731 
UnmarkSkippableJSHolders()732 void XPCJSRuntime::UnmarkSkippableJSHolders() {
733   CycleCollectedJSRuntime::UnmarkSkippableJSHolders();
734 }
735 
PrepareForForgetSkippable()736 void XPCJSRuntime::PrepareForForgetSkippable() {
737   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
738   if (obs) {
739     obs->NotifyObservers(nullptr, "cycle-collector-forget-skippable", nullptr);
740   }
741 }
742 
BeginCycleCollectionCallback()743 void XPCJSRuntime::BeginCycleCollectionCallback() {
744   nsJSContext::BeginCycleCollectionCallback();
745 
746   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
747   if (obs) {
748     obs->NotifyObservers(nullptr, "cycle-collector-begin", nullptr);
749   }
750 }
751 
EndCycleCollectionCallback(CycleCollectorResults & aResults)752 void XPCJSRuntime::EndCycleCollectionCallback(CycleCollectorResults& aResults) {
753   nsJSContext::EndCycleCollectionCallback(aResults);
754 
755   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
756   if (obs) {
757     obs->NotifyObservers(nullptr, "cycle-collector-end", nullptr);
758   }
759 }
760 
DispatchDeferredDeletion(bool aContinuation,bool aPurge)761 void XPCJSRuntime::DispatchDeferredDeletion(bool aContinuation, bool aPurge) {
762   mAsyncSnowWhiteFreer->Start(aContinuation, aPurge);
763 }
764 
xpc_UnmarkSkippableJSHolders()765 void xpc_UnmarkSkippableJSHolders() {
766   if (nsXPConnect::GetRuntimeInstance()) {
767     nsXPConnect::GetRuntimeInstance()->UnmarkSkippableJSHolders();
768   }
769 }
770 
771 /* static */
GCSliceCallback(JSContext * cx,JS::GCProgress progress,const JS::GCDescription & desc)772 void XPCJSRuntime::GCSliceCallback(JSContext* cx, JS::GCProgress progress,
773                                    const JS::GCDescription& desc) {
774   XPCJSRuntime* self = nsXPConnect::GetRuntimeInstance();
775   if (!self) {
776     return;
777   }
778 
779   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
780   if (obs) {
781     switch (progress) {
782       case JS::GC_CYCLE_BEGIN:
783         obs->NotifyObservers(nullptr, "garbage-collector-begin", nullptr);
784         break;
785       case JS::GC_CYCLE_END:
786         obs->NotifyObservers(nullptr, "garbage-collector-end", nullptr);
787         break;
788       default:
789         break;
790     }
791   }
792 
793   CrashReporter::SetGarbageCollecting(progress == JS::GC_CYCLE_BEGIN);
794 
795   if (self->mPrevGCSliceCallback) {
796     (*self->mPrevGCSliceCallback)(cx, progress, desc);
797   }
798 }
799 
800 /* static */
DoCycleCollectionCallback(JSContext * cx)801 void XPCJSRuntime::DoCycleCollectionCallback(JSContext* cx) {
802   // The GC has detected that a CC at this point would collect a tremendous
803   // amount of garbage that is being revivified unnecessarily.
804   NS_DispatchToCurrentThread(
805       NS_NewRunnableFunction("XPCJSRuntime::DoCycleCollectionCallback",
806                              []() { nsJSContext::CycleCollectNow(nullptr); }));
807 
808   XPCJSRuntime* self = nsXPConnect::GetRuntimeInstance();
809   if (!self) {
810     return;
811   }
812 
813   if (self->mPrevDoCycleCollectionCallback) {
814     (*self->mPrevDoCycleCollectionCallback)(cx);
815   }
816 }
817 
CustomGCCallback(JSGCStatus status)818 void XPCJSRuntime::CustomGCCallback(JSGCStatus status) {
819   nsTArray<xpcGCCallback> callbacks(extraGCCallbacks.Clone());
820   for (uint32_t i = 0; i < callbacks.Length(); ++i) {
821     callbacks[i](status);
822   }
823 }
824 
825 /* static */
FinalizeCallback(JSFreeOp * fop,JSFinalizeStatus status,void * data)826 void XPCJSRuntime::FinalizeCallback(JSFreeOp* fop, JSFinalizeStatus status,
827                                     void* data) {
828   XPCJSRuntime* self = nsXPConnect::GetRuntimeInstance();
829   if (!self) {
830     return;
831   }
832 
833   switch (status) {
834     case JSFINALIZE_GROUP_PREPARE: {
835       MOZ_ASSERT(!self->mDoingFinalization, "bad state");
836 
837       MOZ_ASSERT(!self->mGCIsRunning, "bad state");
838       self->mGCIsRunning = true;
839 
840       self->mDoingFinalization = true;
841 
842       break;
843     }
844     case JSFINALIZE_GROUP_START: {
845       MOZ_ASSERT(self->mDoingFinalization, "bad state");
846 
847       MOZ_ASSERT(self->mGCIsRunning, "bad state");
848       self->mGCIsRunning = false;
849 
850       break;
851     }
852     case JSFINALIZE_GROUP_END: {
853       MOZ_ASSERT(self->mDoingFinalization, "bad state");
854       self->mDoingFinalization = false;
855 
856       break;
857     }
858     case JSFINALIZE_COLLECTION_END: {
859       MOZ_ASSERT(!self->mGCIsRunning, "bad state");
860       self->mGCIsRunning = true;
861 
862       if (CycleCollectedJSContext* ccx = self->GetContext()) {
863         const auto* cx = static_cast<const XPCJSContext*>(ccx);
864         if (AutoMarkingPtr* roots = cx->mAutoRoots) {
865           roots->MarkAfterJSFinalizeAll();
866         }
867 
868         // Now we are going to recycle any unused WrappedNativeTearoffs.
869         // We do this by iterating all the live callcontexts
870         // and marking the tearoffs in use. And then we
871         // iterate over all the WrappedNative wrappers and sweep their
872         // tearoffs.
873         //
874         // This allows us to perhaps minimize the growth of the
875         // tearoffs. And also makes us not hold references to interfaces
876         // on our wrapped natives that we are not actually using.
877         //
878         // XXX We may decide to not do this on *every* gc cycle.
879 
880         XPCCallContext* ccxp = cx->GetCallContext();
881         while (ccxp) {
882           // Deal with the strictness of callcontext that
883           // complains if you ask for a tearoff when
884           // it is in a state where the tearoff could not
885           // possibly be valid.
886           if (ccxp->CanGetTearOff()) {
887             XPCWrappedNativeTearOff* to = ccxp->GetTearOff();
888             if (to) {
889               to->Mark();
890             }
891           }
892           ccxp = ccxp->GetPrevCallContext();
893         }
894       }
895 
896       XPCWrappedNativeScope::SweepAllWrappedNativeTearOffs();
897 
898       // Now we need to kill the 'Dying' XPCWrappedNativeProtos.
899       // We transfered these native objects to this table when their
900       // JSObject's were finalized. We did not destroy them immediately
901       // at that point because the ordering of JS finalization is not
902       // deterministic and we did not yet know if any wrappers that
903       // might still be referencing the protos where still yet to be
904       // finalized and destroyed. We *do* know that the protos'
905       // JSObjects would not have been finalized if there were any
906       // wrappers that referenced the proto but where not themselves
907       // slated for finalization in this gc cycle. So... at this point
908       // we know that any and all wrappers that might have been
909       // referencing the protos in the dying list are themselves dead.
910       // So, we can safely delete all the protos in the list.
911 
912       for (auto i = self->mDyingWrappedNativeProtoMap->Iter(); !i.Done();
913            i.Next()) {
914         auto* entry = static_cast<XPCWrappedNativeProtoMap::Entry*>(i.Get());
915         delete static_cast<const XPCWrappedNativeProto*>(entry->key);
916         i.Remove();
917       }
918 
919       MOZ_ASSERT(self->mGCIsRunning, "bad state");
920       self->mGCIsRunning = false;
921 
922       break;
923     }
924   }
925 }
926 
927 /* static */
WeakPointerZonesCallback(JSContext * cx,void * data)928 void XPCJSRuntime::WeakPointerZonesCallback(JSContext* cx, void* data) {
929   // Called before each sweeping slice -- after processing any final marking
930   // triggered by barriers -- to clear out any references to things that are
931   // about to be finalized and update any pointers to moved GC things.
932   XPCJSRuntime* self = static_cast<XPCJSRuntime*>(data);
933 
934   // This callback is always called from within the GC so set the mGCIsRunning
935   // flag to prevent AssertInvalidWrappedJSNotInTable from trying to call back
936   // into the JS API. This has often already been set by FinalizeCallback by the
937   // time we get here, but it may not be if we are doing a shutdown GC or if we
938   // are called for compacting GC.
939   AutoRestore<bool> restoreState(self->mGCIsRunning);
940   self->mGCIsRunning = true;
941 
942   self->mWrappedJSMap->UpdateWeakPointersAfterGC();
943   self->mUAWidgetScopeMap.sweep();
944 }
945 
946 /* static */
WeakPointerCompartmentCallback(JSContext * cx,JS::Compartment * comp,void * data)947 void XPCJSRuntime::WeakPointerCompartmentCallback(JSContext* cx,
948                                                   JS::Compartment* comp,
949                                                   void* data) {
950   // Called immediately after the ZoneGroup weak pointer callback, but only
951   // once for each compartment that is being swept.
952   CompartmentPrivate* xpcComp = CompartmentPrivate::Get(comp);
953   if (xpcComp) {
954     xpcComp->UpdateWeakPointersAfterGC();
955   }
956 }
957 
UpdateWeakPointersAfterGC()958 void CompartmentPrivate::UpdateWeakPointersAfterGC() {
959   mRemoteProxies.sweep();
960   mWrappedJSMap->UpdateWeakPointersAfterGC();
961   mScope->UpdateWeakPointersAfterGC();
962 }
963 
CustomOutOfMemoryCallback()964 void XPCJSRuntime::CustomOutOfMemoryCallback() {
965   if (!Preferences::GetBool("memory.dump_reports_on_oom")) {
966     return;
967   }
968 
969   nsCOMPtr<nsIMemoryInfoDumper> dumper =
970       do_GetService("@mozilla.org/memory-info-dumper;1");
971   if (!dumper) {
972     return;
973   }
974 
975   // If this fails, it fails silently.
976   dumper->DumpMemoryInfoToTempDir(u"due-to-JS-OOM"_ns,
977                                   /* anonymize = */ false,
978                                   /* minimizeMemoryUsage = */ false);
979 }
980 
OnLargeAllocationFailure()981 void XPCJSRuntime::OnLargeAllocationFailure() {
982   CycleCollectedJSRuntime::SetLargeAllocationFailure(OOMState::Reporting);
983 
984   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
985   if (os) {
986     os->NotifyObservers(nullptr, "memory-pressure", u"heap-minimize");
987   }
988 
989   CycleCollectedJSRuntime::SetLargeAllocationFailure(OOMState::Reported);
990 }
991 
992 class LargeAllocationFailureRunnable final : public Runnable {
993   Mutex mMutex;
994   CondVar mCondVar;
995   bool mWaiting;
996 
~LargeAllocationFailureRunnable()997   virtual ~LargeAllocationFailureRunnable() { MOZ_ASSERT(!mWaiting); }
998 
999  protected:
Run()1000   NS_IMETHOD Run() override {
1001     MOZ_ASSERT(NS_IsMainThread());
1002 
1003     XPCJSRuntime::Get()->OnLargeAllocationFailure();
1004 
1005     MutexAutoLock lock(mMutex);
1006     MOZ_ASSERT(mWaiting);
1007 
1008     mWaiting = false;
1009     mCondVar.Notify();
1010     return NS_OK;
1011   }
1012 
1013  public:
LargeAllocationFailureRunnable()1014   LargeAllocationFailureRunnable()
1015       : mozilla::Runnable("LargeAllocationFailureRunnable"),
1016         mMutex("LargeAllocationFailureRunnable::mMutex"),
1017         mCondVar(mMutex, "LargeAllocationFailureRunnable::mCondVar"),
1018         mWaiting(true) {
1019     MOZ_ASSERT(!NS_IsMainThread());
1020   }
1021 
BlockUntilDone()1022   void BlockUntilDone() {
1023     MOZ_ASSERT(!NS_IsMainThread());
1024 
1025     MutexAutoLock lock(mMutex);
1026     while (mWaiting) {
1027       mCondVar.Wait();
1028     }
1029   }
1030 };
1031 
OnLargeAllocationFailureCallback()1032 static void OnLargeAllocationFailureCallback() {
1033   // This callback can be called from any thread, including internal JS helper
1034   // and DOM worker threads. We need to send the low-memory event via the
1035   // observer service which can only be called on the main thread, so proxy to
1036   // the main thread if we're not there already. The purpose of this callback
1037   // is to synchronously free some memory so the caller can retry a failed
1038   // allocation, so block on the completion.
1039 
1040   if (NS_IsMainThread()) {
1041     XPCJSRuntime::Get()->OnLargeAllocationFailure();
1042     return;
1043   }
1044 
1045   RefPtr<LargeAllocationFailureRunnable> r = new LargeAllocationFailureRunnable;
1046   if (NS_WARN_IF(NS_FAILED(NS_DispatchToMainThread(r)))) {
1047     return;
1048   }
1049 
1050   r->BlockUntilDone();
1051 }
1052 
1053 // Usually this is used through nsIPlatformInfo. However, being able to query
1054 // this interface on all threads risk triggering some main-thread assertions
1055 // which is not guaranteed by the callers of GetBuildId.
1056 extern const char gToolkitBuildID[];
1057 
GetBuildId(JS::BuildIdCharVector * aBuildID)1058 bool mozilla::GetBuildId(JS::BuildIdCharVector* aBuildID) {
1059   size_t length = std::char_traits<char>::length(gToolkitBuildID);
1060   return aBuildID->append(gToolkitBuildID, length);
1061 }
1062 
SizeOfIncludingThis(MallocSizeOf mallocSizeOf)1063 size_t XPCJSRuntime::SizeOfIncludingThis(MallocSizeOf mallocSizeOf) {
1064   size_t n = 0;
1065   n += mallocSizeOf(this);
1066   n += mWrappedJSMap->SizeOfIncludingThis(mallocSizeOf);
1067   n += mIID2NativeInterfaceMap->SizeOfIncludingThis(mallocSizeOf);
1068   n += mClassInfo2NativeSetMap->ShallowSizeOfIncludingThis(mallocSizeOf);
1069   n += mNativeSetMap->SizeOfIncludingThis(mallocSizeOf);
1070 
1071   n += CycleCollectedJSRuntime::SizeOfExcludingThis(mallocSizeOf);
1072 
1073   // There are other XPCJSRuntime members that could be measured; the above
1074   // ones have been seen by DMD to be worth measuring.  More stuff may be
1075   // added later.
1076 
1077   return n;
1078 }
1079 
SizeOfIncludingThis(MallocSizeOf mallocSizeOf)1080 size_t CompartmentPrivate::SizeOfIncludingThis(MallocSizeOf mallocSizeOf) {
1081   size_t n = mallocSizeOf(this);
1082   n += mWrappedJSMap->SizeOfIncludingThis(mallocSizeOf);
1083   n += mWrappedJSMap->SizeOfWrappedJS(mallocSizeOf);
1084   return n;
1085 }
1086 
1087 /***************************************************************************/
1088 
SystemIsBeingShutDown()1089 void XPCJSRuntime::SystemIsBeingShutDown() {
1090   // We don't want to track wrapped JS roots after this point since we're
1091   // making them !IsValid anyway through SystemIsBeingShutDown.
1092   while (mWrappedJSRoots) {
1093     mWrappedJSRoots->RemoveFromRootSet();
1094   }
1095 }
1096 
Shutdown(JSContext * cx)1097 void XPCJSRuntime::Shutdown(JSContext* cx) {
1098   // This destructor runs before ~CycleCollectedJSContext, which does the actual
1099   // JS_DestroyContext() call. But destroying the context triggers one final GC,
1100   // which can call back into the context with various callbacks if we aren't
1101   // careful. Remove the relevant callbacks, but leave the weak pointer
1102   // callbacks to clear out any remaining table entries.
1103   JS_RemoveFinalizeCallback(cx, FinalizeCallback);
1104   xpc_DelocalizeRuntime(JS_GetRuntime(cx));
1105 
1106   JS::SetGCSliceCallback(cx, mPrevGCSliceCallback);
1107 
1108   nsScriptSecurityManager::ClearJSCallbacks(cx);
1109 
1110   // Clean up and destroy maps. Any remaining entries in mWrappedJSMap will be
1111   // cleaned up by the weak pointer callbacks.
1112   mIID2NativeInterfaceMap = nullptr;
1113 
1114   mClassInfo2NativeSetMap = nullptr;
1115 
1116   mNativeSetMap = nullptr;
1117 
1118   mDyingWrappedNativeProtoMap = nullptr;
1119 
1120   // Prevent ~LinkedList assertion failures if we leaked things.
1121   mWrappedNativeScopes.clear();
1122 
1123   CycleCollectedJSRuntime::Shutdown(cx);
1124 }
1125 
~XPCJSRuntime()1126 XPCJSRuntime::~XPCJSRuntime() {
1127   MOZ_COUNT_DTOR_INHERITED(XPCJSRuntime, CycleCollectedJSRuntime);
1128 }
1129 
1130 // If |*anonymizeID| is non-zero and this is a user realm, the name will
1131 // be anonymized.
GetRealmName(JS::Realm * realm,nsCString & name,int * anonymizeID,bool replaceSlashes)1132 static void GetRealmName(JS::Realm* realm, nsCString& name, int* anonymizeID,
1133                          bool replaceSlashes) {
1134   if (*anonymizeID && !js::IsSystemRealm(realm)) {
1135     name.AppendPrintf("<anonymized-%d>", *anonymizeID);
1136     *anonymizeID += 1;
1137   } else if (JSPrincipals* principals = JS::GetRealmPrincipals(realm)) {
1138     nsresult rv = nsJSPrincipals::get(principals)->GetScriptLocation(name);
1139     if (NS_FAILED(rv)) {
1140       name.AssignLiteral("(unknown)");
1141     }
1142 
1143     // If the realm's location (name) differs from the principal's script
1144     // location, append the realm's location to allow differentiation of
1145     // multiple realms owned by the same principal (e.g. components owned
1146     // by the system or null principal).
1147     RealmPrivate* realmPrivate = RealmPrivate::Get(realm);
1148     if (realmPrivate) {
1149       const nsACString& location = realmPrivate->GetLocation();
1150       if (!location.IsEmpty() && !location.Equals(name)) {
1151         name.AppendLiteral(", ");
1152         name.Append(location);
1153       }
1154     }
1155 
1156     if (*anonymizeID) {
1157       // We might have a file:// URL that includes a path from the local
1158       // filesystem, which should be omitted if we're anonymizing.
1159       static const char* filePrefix = "file://";
1160       int filePos = name.Find(filePrefix);
1161       if (filePos >= 0) {
1162         int pathPos = filePos + strlen(filePrefix);
1163         int lastSlashPos = -1;
1164         for (int i = pathPos; i < int(name.Length()); i++) {
1165           if (name[i] == '/' || name[i] == '\\') {
1166             lastSlashPos = i;
1167           }
1168         }
1169         if (lastSlashPos != -1) {
1170           name.ReplaceLiteral(pathPos, lastSlashPos - pathPos, "<anonymized>");
1171         } else {
1172           // Something went wrong. Anonymize the entire path to be
1173           // safe.
1174           name.Truncate(pathPos);
1175           name += "<anonymized?!>";
1176         }
1177       }
1178 
1179       // We might have a location like this:
1180       //   inProcessBrowserChildGlobal?ownedBy=http://www.example.com/
1181       // The owner should be omitted if it's not a chrome: URI and we're
1182       // anonymizing.
1183       static const char* ownedByPrefix = "inProcessBrowserChildGlobal?ownedBy=";
1184       int ownedByPos = name.Find(ownedByPrefix);
1185       if (ownedByPos >= 0) {
1186         const char* chrome = "chrome:";
1187         int ownerPos = ownedByPos + strlen(ownedByPrefix);
1188         const nsDependentCSubstring& ownerFirstPart =
1189             Substring(name, ownerPos, strlen(chrome));
1190         if (!ownerFirstPart.EqualsASCII(chrome)) {
1191           name.Truncate(ownerPos);
1192           name += "<anonymized>";
1193         }
1194       }
1195     }
1196 
1197     // A hack: replace forward slashes with '\\' so they aren't
1198     // treated as path separators.  Users of the reporters
1199     // (such as about:memory) have to undo this change.
1200     if (replaceSlashes) {
1201       name.ReplaceChar('/', '\\');
1202     }
1203   } else {
1204     name.AssignLiteral("null-principal");
1205   }
1206 }
1207 
GetCurrentRealmName(JSContext * cx,nsCString & name)1208 extern void xpc::GetCurrentRealmName(JSContext* cx, nsCString& name) {
1209   RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
1210   if (!global) {
1211     name.AssignLiteral("no global");
1212     return;
1213   }
1214 
1215   JS::Realm* realm = GetNonCCWObjectRealm(global);
1216   int anonymizeID = 0;
1217   GetRealmName(realm, name, &anonymizeID, false);
1218 }
1219 
AddGCCallback(xpcGCCallback cb)1220 void xpc::AddGCCallback(xpcGCCallback cb) {
1221   XPCJSRuntime::Get()->AddGCCallback(cb);
1222 }
1223 
RemoveGCCallback(xpcGCCallback cb)1224 void xpc::RemoveGCCallback(xpcGCCallback cb) {
1225   XPCJSRuntime::Get()->RemoveGCCallback(cb);
1226 }
1227 
JSMainRuntimeGCHeapDistinguishedAmount()1228 static int64_t JSMainRuntimeGCHeapDistinguishedAmount() {
1229   JSContext* cx = danger::GetJSContext();
1230   return int64_t(JS_GetGCParameter(cx, JSGC_TOTAL_CHUNKS)) * js::gc::ChunkSize;
1231 }
1232 
JSMainRuntimeTemporaryPeakDistinguishedAmount()1233 static int64_t JSMainRuntimeTemporaryPeakDistinguishedAmount() {
1234   JSContext* cx = danger::GetJSContext();
1235   return JS::PeakSizeOfTemporary(cx);
1236 }
1237 
JSMainRuntimeCompartmentsSystemDistinguishedAmount()1238 static int64_t JSMainRuntimeCompartmentsSystemDistinguishedAmount() {
1239   JSContext* cx = danger::GetJSContext();
1240   return JS::SystemCompartmentCount(cx);
1241 }
1242 
JSMainRuntimeCompartmentsUserDistinguishedAmount()1243 static int64_t JSMainRuntimeCompartmentsUserDistinguishedAmount() {
1244   JSContext* cx = XPCJSContext::Get()->Context();
1245   return JS::UserCompartmentCount(cx);
1246 }
1247 
JSMainRuntimeRealmsSystemDistinguishedAmount()1248 static int64_t JSMainRuntimeRealmsSystemDistinguishedAmount() {
1249   JSContext* cx = danger::GetJSContext();
1250   return JS::SystemRealmCount(cx);
1251 }
1252 
JSMainRuntimeRealmsUserDistinguishedAmount()1253 static int64_t JSMainRuntimeRealmsUserDistinguishedAmount() {
1254   JSContext* cx = XPCJSContext::Get()->Context();
1255   return JS::UserRealmCount(cx);
1256 }
1257 
1258 class JSMainRuntimeTemporaryPeakReporter final : public nsIMemoryReporter {
1259   ~JSMainRuntimeTemporaryPeakReporter() = default;
1260 
1261  public:
1262   NS_DECL_ISUPPORTS
1263 
CollectReports(nsIHandleReportCallback * aHandleReport,nsISupports * aData,bool aAnonymize)1264   NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
1265                             nsISupports* aData, bool aAnonymize) override {
1266     MOZ_COLLECT_REPORT(
1267         "js-main-runtime-temporary-peak", KIND_OTHER, UNITS_BYTES,
1268         JSMainRuntimeTemporaryPeakDistinguishedAmount(),
1269         "Peak transient data size in the main JSRuntime (the current size "
1270         "of which is reported as "
1271         "'explicit/js-non-window/runtime/temporary').");
1272 
1273     return NS_OK;
1274   }
1275 };
1276 
1277 NS_IMPL_ISUPPORTS(JSMainRuntimeTemporaryPeakReporter, nsIMemoryReporter)
1278 
1279 // The REPORT* macros do an unconditional report.  The ZRREPORT* macros are for
1280 // realms and zones; they aggregate any entries smaller than
1281 // SUNDRIES_THRESHOLD into the "sundries/gc-heap" and "sundries/malloc-heap"
1282 // entries for the realm.
1283 
1284 #define SUNDRIES_THRESHOLD js::MemoryReportingSundriesThreshold()
1285 
1286 #define REPORT(_path, _kind, _units, _amount, _desc)             \
1287   handleReport->Callback(""_ns, _path, nsIMemoryReporter::_kind, \
1288                          nsIMemoryReporter::_units, _amount,     \
1289                          nsLiteralCString(_desc), data);
1290 
1291 #define REPORT_BYTES(_path, _kind, _amount, _desc) \
1292   REPORT(_path, _kind, UNITS_BYTES, _amount, _desc);
1293 
1294 #define REPORT_GC_BYTES(_path, _amount, _desc)                            \
1295   do {                                                                    \
1296     size_t amount = _amount; /* evaluate _amount only once */             \
1297     handleReport->Callback(""_ns, _path, nsIMemoryReporter::KIND_NONHEAP, \
1298                            nsIMemoryReporter::UNITS_BYTES, amount,        \
1299                            nsLiteralCString(_desc), data);                \
1300     gcTotal += amount;                                                    \
1301   } while (0)
1302 
1303 // Report realm/zone non-GC (KIND_HEAP) bytes.
1304 #define ZRREPORT_BYTES(_path, _amount, _desc)                            \
1305   do {                                                                   \
1306     /* Assign _descLiteral plus "" into a char* to prove that it's */    \
1307     /* actually a literal. */                                            \
1308     size_t amount = _amount; /* evaluate _amount only once */            \
1309     if (amount >= SUNDRIES_THRESHOLD) {                                  \
1310       handleReport->Callback(""_ns, _path, nsIMemoryReporter::KIND_HEAP, \
1311                              nsIMemoryReporter::UNITS_BYTES, amount,     \
1312                              nsLiteralCString(_desc), data);             \
1313     } else {                                                             \
1314       sundriesMallocHeap += amount;                                      \
1315     }                                                                    \
1316   } while (0)
1317 
1318 // Report realm/zone GC bytes.
1319 #define ZRREPORT_GC_BYTES(_path, _amount, _desc)                            \
1320   do {                                                                      \
1321     size_t amount = _amount; /* evaluate _amount only once */               \
1322     if (amount >= SUNDRIES_THRESHOLD) {                                     \
1323       handleReport->Callback(""_ns, _path, nsIMemoryReporter::KIND_NONHEAP, \
1324                              nsIMemoryReporter::UNITS_BYTES, amount,        \
1325                              nsLiteralCString(_desc), data);                \
1326       gcTotal += amount;                                                    \
1327     } else {                                                                \
1328       sundriesGCHeap += amount;                                             \
1329     }                                                                       \
1330   } while (0)
1331 
1332 // Report realm/zone non-heap bytes.
1333 #define ZRREPORT_NONHEAP_BYTES(_path, _amount, _desc)                       \
1334   do {                                                                      \
1335     size_t amount = _amount; /* evaluate _amount only once */               \
1336     if (amount >= SUNDRIES_THRESHOLD) {                                     \
1337       handleReport->Callback(""_ns, _path, nsIMemoryReporter::KIND_NONHEAP, \
1338                              nsIMemoryReporter::UNITS_BYTES, amount,        \
1339                              nsLiteralCString(_desc), data);                \
1340     } else {                                                                \
1341       sundriesNonHeap += amount;                                            \
1342     }                                                                       \
1343   } while (0)
1344 
1345 // Report runtime bytes.
1346 #define RREPORT_BYTES(_path, _kind, _amount, _desc)                \
1347   do {                                                             \
1348     size_t amount = _amount; /* evaluate _amount only once */      \
1349     handleReport->Callback(""_ns, _path, nsIMemoryReporter::_kind, \
1350                            nsIMemoryReporter::UNITS_BYTES, amount, \
1351                            nsLiteralCString(_desc), data);         \
1352     rtTotal += amount;                                             \
1353   } while (0)
1354 
1355 // Report GC thing bytes.
1356 #define MREPORT_BYTES(_path, _kind, _amount, _desc)                \
1357   do {                                                             \
1358     size_t amount = _amount; /* evaluate _amount only once */      \
1359     handleReport->Callback(""_ns, _path, nsIMemoryReporter::_kind, \
1360                            nsIMemoryReporter::UNITS_BYTES, amount, \
1361                            nsLiteralCString(_desc), data);         \
1362     gcThingTotal += amount;                                        \
1363   } while (0)
1364 
1365 MOZ_DEFINE_MALLOC_SIZE_OF(JSMallocSizeOf)
1366 
1367 namespace xpc {
1368 
ReportZoneStats(const JS::ZoneStats & zStats,const xpc::ZoneStatsExtras & extras,nsIHandleReportCallback * handleReport,nsISupports * data,bool anonymize,size_t * gcTotalOut=nullptr)1369 static void ReportZoneStats(const JS::ZoneStats& zStats,
1370                             const xpc::ZoneStatsExtras& extras,
1371                             nsIHandleReportCallback* handleReport,
1372                             nsISupports* data, bool anonymize,
1373                             size_t* gcTotalOut = nullptr) {
1374   const nsCString& pathPrefix = extras.pathPrefix;
1375   size_t gcTotal = 0;
1376   size_t sundriesGCHeap = 0;
1377   size_t sundriesMallocHeap = 0;
1378   size_t sundriesNonHeap = 0;
1379 
1380   MOZ_ASSERT(!gcTotalOut == zStats.isTotals);
1381 
1382   ZRREPORT_GC_BYTES(pathPrefix + "symbols/gc-heap"_ns, zStats.symbolsGCHeap,
1383                     "Symbols.");
1384 
1385   ZRREPORT_GC_BYTES(
1386       pathPrefix + "gc-heap-arena-admin"_ns, zStats.gcHeapArenaAdmin,
1387       "Bookkeeping information and alignment padding within GC arenas.");
1388 
1389   ZRREPORT_GC_BYTES(pathPrefix + "unused-gc-things"_ns,
1390                     zStats.unusedGCThings.totalSize(),
1391                     "Unused GC thing cells within non-empty arenas.");
1392 
1393   ZRREPORT_BYTES(pathPrefix + "unique-id-map"_ns, zStats.uniqueIdMap,
1394                  "Address-independent cell identities.");
1395 
1396   ZRREPORT_BYTES(pathPrefix + "propmap-tables"_ns, zStats.initialPropMapTable,
1397                  "Tables storing property map information.");
1398 
1399   ZRREPORT_BYTES(pathPrefix + "shape-tables"_ns, zStats.shapeTables,
1400                  "Tables storing shape information.");
1401 
1402   ZRREPORT_BYTES(pathPrefix + "compartments/compartment-objects"_ns,
1403                  zStats.compartmentObjects,
1404                  "The JS::Compartment objects in this zone.");
1405 
1406   ZRREPORT_BYTES(
1407       pathPrefix + "compartments/cross-compartment-wrapper-tables"_ns,
1408       zStats.crossCompartmentWrappersTables,
1409       "The cross-compartment wrapper tables.");
1410 
1411   ZRREPORT_BYTES(
1412       pathPrefix + "compartments/private-data"_ns,
1413       zStats.compartmentsPrivateData,
1414       "Extra data attached to each compartment by XPConnect, including "
1415       "its wrapped-js.");
1416 
1417   ZRREPORT_GC_BYTES(pathPrefix + "jit-codes-gc-heap"_ns, zStats.jitCodesGCHeap,
1418                     "References to executable code pools used by the JITs.");
1419 
1420   ZRREPORT_GC_BYTES(pathPrefix + "getter-setters-gc-heap"_ns,
1421                     zStats.getterSettersGCHeap,
1422                     "Information for getter/setter properties.");
1423 
1424   ZRREPORT_GC_BYTES(pathPrefix + "property-maps/gc-heap/compact"_ns,
1425                     zStats.compactPropMapsGCHeap,
1426                     "Information about object properties.");
1427 
1428   ZRREPORT_GC_BYTES(pathPrefix + "property-maps/gc-heap/normal"_ns,
1429                     zStats.normalPropMapsGCHeap,
1430                     "Information about object properties.");
1431 
1432   ZRREPORT_GC_BYTES(pathPrefix + "property-maps/gc-heap/dict"_ns,
1433                     zStats.dictPropMapsGCHeap,
1434                     "Information about dictionary mode object properties.");
1435 
1436   ZRREPORT_BYTES(pathPrefix + "property-maps/malloc-heap/children"_ns,
1437                  zStats.propMapChildren, "Tables for PropMap children.");
1438 
1439   ZRREPORT_BYTES(pathPrefix + "property-maps/malloc-heap/tables"_ns,
1440                  zStats.propMapTables, "HashTables for PropMaps.");
1441 
1442   ZRREPORT_GC_BYTES(pathPrefix + "scopes/gc-heap"_ns, zStats.scopesGCHeap,
1443                     "Scope information for scripts.");
1444 
1445   ZRREPORT_BYTES(pathPrefix + "scopes/malloc-heap"_ns, zStats.scopesMallocHeap,
1446                  "Arrays of binding names and other binding-related data.");
1447 
1448   ZRREPORT_GC_BYTES(pathPrefix + "regexp-shareds/gc-heap"_ns,
1449                     zStats.regExpSharedsGCHeap, "Shared compiled regexp data.");
1450 
1451   ZRREPORT_BYTES(pathPrefix + "regexp-shareds/malloc-heap"_ns,
1452                  zStats.regExpSharedsMallocHeap,
1453                  "Shared compiled regexp data.");
1454 
1455   ZRREPORT_BYTES(pathPrefix + "regexp-zone"_ns, zStats.regexpZone,
1456                  "The regexp zone and regexp data.");
1457 
1458   ZRREPORT_BYTES(pathPrefix + "jit-zone"_ns, zStats.jitZone, "The JIT zone.");
1459 
1460   ZRREPORT_BYTES(pathPrefix + "baseline/optimized-stubs"_ns,
1461                  zStats.baselineStubsOptimized,
1462                  "The Baseline JIT's optimized IC stubs (excluding code).");
1463 
1464   ZRREPORT_BYTES(pathPrefix + "script-counts-map"_ns, zStats.scriptCountsMap,
1465                  "Profiling-related information for scripts.");
1466 
1467   ZRREPORT_NONHEAP_BYTES(pathPrefix + "code/ion"_ns, zStats.code.ion,
1468                          "Code generated by the IonMonkey JIT.");
1469 
1470   ZRREPORT_NONHEAP_BYTES(pathPrefix + "code/baseline"_ns, zStats.code.baseline,
1471                          "Code generated by the Baseline JIT.");
1472 
1473   ZRREPORT_NONHEAP_BYTES(pathPrefix + "code/regexp"_ns, zStats.code.regexp,
1474                          "Code generated by the regexp JIT.");
1475 
1476   ZRREPORT_NONHEAP_BYTES(
1477       pathPrefix + "code/other"_ns, zStats.code.other,
1478       "Code generated by the JITs for wrappers and trampolines.");
1479 
1480   ZRREPORT_NONHEAP_BYTES(pathPrefix + "code/unused"_ns, zStats.code.unused,
1481                          "Memory allocated by one of the JITs to hold code, "
1482                          "but which is currently unused.");
1483 
1484   size_t stringsNotableAboutMemoryGCHeap = 0;
1485   size_t stringsNotableAboutMemoryMallocHeap = 0;
1486 
1487 #define MAYBE_INLINE "The characters may be inline or on the malloc heap."
1488 #define MAYBE_OVERALLOCATED \
1489   "Sometimes over-allocated to simplify string concatenation."
1490 
1491   for (size_t i = 0; i < zStats.notableStrings.length(); i++) {
1492     const JS::NotableStringInfo& info = zStats.notableStrings[i];
1493 
1494     MOZ_ASSERT(!zStats.isTotals);
1495 
1496     // We don't do notable string detection when anonymizing, because
1497     // there's a good chance its for crash submission, and the memory
1498     // required for notable string detection is high.
1499     MOZ_ASSERT(!anonymize);
1500 
1501     nsDependentCString notableString(info.buffer.get());
1502 
1503     // Viewing about:memory generates many notable strings which contain
1504     // "string(length=".  If we report these as notable, then we'll create
1505     // even more notable strings the next time we open about:memory (unless
1506     // there's a GC in the meantime), and so on ad infinitum.
1507     //
1508     // To avoid cluttering up about:memory like this, we stick notable
1509     // strings which contain "string(length=" into their own bucket.
1510 #define STRING_LENGTH "string(length="
1511     if (FindInReadable(nsLiteralCString(STRING_LENGTH), notableString)) {
1512       stringsNotableAboutMemoryGCHeap += info.gcHeapLatin1;
1513       stringsNotableAboutMemoryGCHeap += info.gcHeapTwoByte;
1514       stringsNotableAboutMemoryMallocHeap += info.mallocHeapLatin1;
1515       stringsNotableAboutMemoryMallocHeap += info.mallocHeapTwoByte;
1516       continue;
1517     }
1518 
1519     // Escape / to \ before we put notableString into the memory reporter
1520     // path, because we don't want any forward slashes in the string to
1521     // count as path separators.
1522     nsCString escapedString(notableString);
1523     escapedString.ReplaceSubstring("/", "\\");
1524 
1525     bool truncated = notableString.Length() < info.length;
1526 
1527     nsCString path =
1528         pathPrefix +
1529         nsPrintfCString("strings/" STRING_LENGTH "%zu, copies=%d, \"%s\"%s)/",
1530                         info.length, info.numCopies, escapedString.get(),
1531                         truncated ? " (truncated)" : "");
1532 
1533     if (info.gcHeapLatin1 > 0) {
1534       REPORT_GC_BYTES(path + "gc-heap/latin1"_ns, info.gcHeapLatin1,
1535                       "Latin1 strings. " MAYBE_INLINE);
1536     }
1537 
1538     if (info.gcHeapTwoByte > 0) {
1539       REPORT_GC_BYTES(path + "gc-heap/two-byte"_ns, info.gcHeapTwoByte,
1540                       "TwoByte strings. " MAYBE_INLINE);
1541     }
1542 
1543     if (info.mallocHeapLatin1 > 0) {
1544       REPORT_BYTES(path + "malloc-heap/latin1"_ns, KIND_HEAP,
1545                    info.mallocHeapLatin1,
1546                    "Non-inline Latin1 string characters. " MAYBE_OVERALLOCATED);
1547     }
1548 
1549     if (info.mallocHeapTwoByte > 0) {
1550       REPORT_BYTES(
1551           path + "malloc-heap/two-byte"_ns, KIND_HEAP, info.mallocHeapTwoByte,
1552           "Non-inline TwoByte string characters. " MAYBE_OVERALLOCATED);
1553     }
1554   }
1555 
1556   nsCString nonNotablePath = pathPrefix;
1557   nonNotablePath += (zStats.isTotals || anonymize)
1558                         ? "strings/"_ns
1559                         : "strings/string(<non-notable strings>)/"_ns;
1560 
1561   if (zStats.stringInfo.gcHeapLatin1 > 0) {
1562     REPORT_GC_BYTES(nonNotablePath + "gc-heap/latin1"_ns,
1563                     zStats.stringInfo.gcHeapLatin1,
1564                     "Latin1 strings. " MAYBE_INLINE);
1565   }
1566 
1567   if (zStats.stringInfo.gcHeapTwoByte > 0) {
1568     REPORT_GC_BYTES(nonNotablePath + "gc-heap/two-byte"_ns,
1569                     zStats.stringInfo.gcHeapTwoByte,
1570                     "TwoByte strings. " MAYBE_INLINE);
1571   }
1572 
1573   if (zStats.stringInfo.mallocHeapLatin1 > 0) {
1574     REPORT_BYTES(nonNotablePath + "malloc-heap/latin1"_ns, KIND_HEAP,
1575                  zStats.stringInfo.mallocHeapLatin1,
1576                  "Non-inline Latin1 string characters. " MAYBE_OVERALLOCATED);
1577   }
1578 
1579   if (zStats.stringInfo.mallocHeapTwoByte > 0) {
1580     REPORT_BYTES(nonNotablePath + "malloc-heap/two-byte"_ns, KIND_HEAP,
1581                  zStats.stringInfo.mallocHeapTwoByte,
1582                  "Non-inline TwoByte string characters. " MAYBE_OVERALLOCATED);
1583   }
1584 
1585   if (stringsNotableAboutMemoryGCHeap > 0) {
1586     MOZ_ASSERT(!zStats.isTotals);
1587     REPORT_GC_BYTES(
1588         pathPrefix + "strings/string(<about-memory>)/gc-heap"_ns,
1589         stringsNotableAboutMemoryGCHeap,
1590         "Strings that contain the characters '" STRING_LENGTH
1591         "', which "
1592         "are probably from about:memory itself." MAYBE_INLINE
1593         " We filter them out rather than display them, because displaying "
1594         "them would create even more such strings every time about:memory "
1595         "is refreshed.");
1596   }
1597 
1598   if (stringsNotableAboutMemoryMallocHeap > 0) {
1599     MOZ_ASSERT(!zStats.isTotals);
1600     REPORT_BYTES(
1601         pathPrefix + "strings/string(<about-memory>)/malloc-heap"_ns, KIND_HEAP,
1602         stringsNotableAboutMemoryMallocHeap,
1603         "Non-inline string characters of strings that contain the "
1604         "characters '" STRING_LENGTH
1605         "', which are probably from "
1606         "about:memory itself. " MAYBE_OVERALLOCATED
1607         " We filter them out rather than display them, because displaying "
1608         "them would create even more such strings every time about:memory "
1609         "is refreshed.");
1610   }
1611 
1612   const JS::ShapeInfo& shapeInfo = zStats.shapeInfo;
1613   if (shapeInfo.shapesGCHeapShared > 0) {
1614     REPORT_GC_BYTES(pathPrefix + "shapes/gc-heap/shared"_ns,
1615                     shapeInfo.shapesGCHeapShared, "Shared shapes.");
1616   }
1617 
1618   if (shapeInfo.shapesGCHeapDict > 0) {
1619     REPORT_GC_BYTES(pathPrefix + "shapes/gc-heap/dict"_ns,
1620                     shapeInfo.shapesGCHeapDict, "Shapes in dictionary mode.");
1621   }
1622 
1623   if (shapeInfo.shapesGCHeapBase > 0) {
1624     REPORT_GC_BYTES(pathPrefix + "shapes/gc-heap/base"_ns,
1625                     shapeInfo.shapesGCHeapBase,
1626                     "Base shapes, which collate data common to many shapes.");
1627   }
1628 
1629   if (shapeInfo.shapesMallocHeapCache > 0) {
1630     REPORT_BYTES(pathPrefix + "shapes/malloc-heap/shape-cache"_ns, KIND_HEAP,
1631                  shapeInfo.shapesMallocHeapCache,
1632                  "Shape cache hash set for adding properties.");
1633   }
1634 
1635   if (sundriesGCHeap > 0) {
1636     // We deliberately don't use ZRREPORT_GC_BYTES here.
1637     REPORT_GC_BYTES(
1638         pathPrefix + "sundries/gc-heap"_ns, sundriesGCHeap,
1639         "The sum of all 'gc-heap' measurements that are too small to be "
1640         "worth showing individually.");
1641   }
1642 
1643   if (sundriesMallocHeap > 0) {
1644     // We deliberately don't use ZRREPORT_BYTES here.
1645     REPORT_BYTES(
1646         pathPrefix + "sundries/malloc-heap"_ns, KIND_HEAP, sundriesMallocHeap,
1647         "The sum of all 'malloc-heap' measurements that are too small to "
1648         "be worth showing individually.");
1649   }
1650 
1651   if (sundriesNonHeap > 0) {
1652     // We deliberately don't use ZRREPORT_NONHEAP_BYTES here.
1653     REPORT_BYTES(pathPrefix + "sundries/other-heap"_ns, KIND_NONHEAP,
1654                  sundriesNonHeap,
1655                  "The sum of non-malloc/gc measurements that are too small to "
1656                  "be worth showing individually.");
1657   }
1658 
1659   if (gcTotalOut) {
1660     *gcTotalOut += gcTotal;
1661   }
1662 
1663 #undef STRING_LENGTH
1664 }
1665 
ReportClassStats(const ClassInfo & classInfo,const nsACString & path,nsIHandleReportCallback * handleReport,nsISupports * data,size_t & gcTotal)1666 static void ReportClassStats(const ClassInfo& classInfo, const nsACString& path,
1667                              nsIHandleReportCallback* handleReport,
1668                              nsISupports* data, size_t& gcTotal) {
1669   // We deliberately don't use ZRREPORT_BYTES, so that these per-class values
1670   // don't go into sundries.
1671 
1672   if (classInfo.objectsGCHeap > 0) {
1673     REPORT_GC_BYTES(path + "objects/gc-heap"_ns, classInfo.objectsGCHeap,
1674                     "Objects, including fixed slots.");
1675   }
1676 
1677   if (classInfo.objectsMallocHeapSlots > 0) {
1678     REPORT_BYTES(path + "objects/malloc-heap/slots"_ns, KIND_HEAP,
1679                  classInfo.objectsMallocHeapSlots, "Non-fixed object slots.");
1680   }
1681 
1682   if (classInfo.objectsMallocHeapElementsNormal > 0) {
1683     REPORT_BYTES(path + "objects/malloc-heap/elements/normal"_ns, KIND_HEAP,
1684                  classInfo.objectsMallocHeapElementsNormal,
1685                  "Normal (non-wasm) indexed elements.");
1686   }
1687 
1688   if (classInfo.objectsMallocHeapElementsAsmJS > 0) {
1689     REPORT_BYTES(path + "objects/malloc-heap/elements/asm.js"_ns, KIND_HEAP,
1690                  classInfo.objectsMallocHeapElementsAsmJS,
1691                  "asm.js array buffer elements allocated in the malloc heap.");
1692   }
1693 
1694   if (classInfo.objectsMallocHeapMisc > 0) {
1695     REPORT_BYTES(path + "objects/malloc-heap/misc"_ns, KIND_HEAP,
1696                  classInfo.objectsMallocHeapMisc, "Miscellaneous object data.");
1697   }
1698 
1699   if (classInfo.objectsNonHeapElementsNormal > 0) {
1700     REPORT_BYTES(path + "objects/non-heap/elements/normal"_ns, KIND_NONHEAP,
1701                  classInfo.objectsNonHeapElementsNormal,
1702                  "Memory-mapped non-shared array buffer elements.");
1703   }
1704 
1705   if (classInfo.objectsNonHeapElementsShared > 0) {
1706     REPORT_BYTES(
1707         path + "objects/non-heap/elements/shared"_ns, KIND_NONHEAP,
1708         classInfo.objectsNonHeapElementsShared,
1709         "Memory-mapped shared array buffer elements. These elements are "
1710         "shared between one or more runtimes; the reported size is divided "
1711         "by the buffer's refcount.");
1712   }
1713 
1714   // WebAssembly memories are always non-heap-allocated (mmap). We never put
1715   // these under sundries, because (a) in practice they're almost always
1716   // larger than the sundries threshold, and (b) we'd need a third category of
1717   // sundries ("non-heap"), which would be a pain.
1718   if (classInfo.objectsNonHeapElementsWasm > 0) {
1719     REPORT_BYTES(path + "objects/non-heap/elements/wasm"_ns, KIND_NONHEAP,
1720                  classInfo.objectsNonHeapElementsWasm,
1721                  "wasm/asm.js array buffer elements allocated outside both the "
1722                  "malloc heap and the GC heap.");
1723   }
1724 
1725   if (classInfo.objectsNonHeapCodeWasm > 0) {
1726     REPORT_BYTES(path + "objects/non-heap/code/wasm"_ns, KIND_NONHEAP,
1727                  classInfo.objectsNonHeapCodeWasm,
1728                  "AOT-compiled wasm/asm.js code.");
1729   }
1730 
1731   // Although wasm guard pages aren't committed in memory they can be very
1732   // large and contribute greatly to vsize and so are worth reporting.
1733   if (classInfo.wasmGuardPages > 0) {
1734     REPORT_BYTES(
1735         "wasm-guard-pages"_ns, KIND_OTHER, classInfo.wasmGuardPages,
1736         "Guard pages mapped after the end of wasm memories, reserved for "
1737         "optimization tricks, but not committed and thus never contributing"
1738         " to RSS, only vsize.");
1739   }
1740 }
1741 
ReportRealmStats(const JS::RealmStats & realmStats,const xpc::RealmStatsExtras & extras,nsIHandleReportCallback * handleReport,nsISupports * data,size_t * gcTotalOut=nullptr)1742 static void ReportRealmStats(const JS::RealmStats& realmStats,
1743                              const xpc::RealmStatsExtras& extras,
1744                              nsIHandleReportCallback* handleReport,
1745                              nsISupports* data, size_t* gcTotalOut = nullptr) {
1746   static const nsDependentCString addonPrefix("explicit/add-ons/");
1747 
1748   size_t gcTotal = 0, sundriesGCHeap = 0, sundriesMallocHeap = 0;
1749   nsAutoCString realmJSPathPrefix(extras.jsPathPrefix);
1750   nsAutoCString realmDOMPathPrefix(extras.domPathPrefix);
1751 
1752   MOZ_ASSERT(!gcTotalOut == realmStats.isTotals);
1753 
1754   nsCString nonNotablePath = realmJSPathPrefix;
1755   nonNotablePath += realmStats.isTotals
1756                         ? "classes/"_ns
1757                         : "classes/class(<non-notable classes>)/"_ns;
1758 
1759   ReportClassStats(realmStats.classInfo, nonNotablePath, handleReport, data,
1760                    gcTotal);
1761 
1762   for (size_t i = 0; i < realmStats.notableClasses.length(); i++) {
1763     MOZ_ASSERT(!realmStats.isTotals);
1764     const JS::NotableClassInfo& classInfo = realmStats.notableClasses[i];
1765 
1766     nsCString classPath =
1767         realmJSPathPrefix +
1768         nsPrintfCString("classes/class(%s)/", classInfo.className_.get());
1769 
1770     ReportClassStats(classInfo, classPath, handleReport, data, gcTotal);
1771   }
1772 
1773   // Note that we use realmDOMPathPrefix here.  This is because we measure
1774   // orphan DOM nodes in the JS reporter, but we want to report them in a "dom"
1775   // sub-tree rather than a "js" sub-tree.
1776   ZRREPORT_BYTES(
1777       realmDOMPathPrefix + "orphan-nodes"_ns, realmStats.objectsPrivate,
1778       "Orphan DOM nodes, i.e. those that are only reachable from JavaScript "
1779       "objects.");
1780 
1781   ZRREPORT_GC_BYTES(
1782       realmJSPathPrefix + "scripts/gc-heap"_ns, realmStats.scriptsGCHeap,
1783       "JSScript instances. There is one per user-defined function in a "
1784       "script, and one for the top-level code in a script.");
1785 
1786   ZRREPORT_BYTES(realmJSPathPrefix + "scripts/malloc-heap/data"_ns,
1787                  realmStats.scriptsMallocHeapData,
1788                  "Various variable-length tables in JSScripts.");
1789 
1790   ZRREPORT_BYTES(realmJSPathPrefix + "baseline/data"_ns,
1791                  realmStats.baselineData,
1792                  "The Baseline JIT's compilation data (BaselineScripts).");
1793 
1794   ZRREPORT_BYTES(realmJSPathPrefix + "baseline/fallback-stubs"_ns,
1795                  realmStats.baselineStubsFallback,
1796                  "The Baseline JIT's fallback IC stubs (excluding code).");
1797 
1798   ZRREPORT_BYTES(realmJSPathPrefix + "ion-data"_ns, realmStats.ionData,
1799                  "The IonMonkey JIT's compilation data (IonScripts).");
1800 
1801   ZRREPORT_BYTES(realmJSPathPrefix + "jit-scripts"_ns, realmStats.jitScripts,
1802                  "JIT data associated with scripts.");
1803 
1804   ZRREPORT_BYTES(realmJSPathPrefix + "realm-object"_ns, realmStats.realmObject,
1805                  "The JS::Realm object itself.");
1806 
1807   ZRREPORT_BYTES(
1808       realmJSPathPrefix + "realm-tables"_ns, realmStats.realmTables,
1809       "Realm-wide tables storing object group information and wasm instances.");
1810 
1811   ZRREPORT_BYTES(realmJSPathPrefix + "inner-views"_ns,
1812                  realmStats.innerViewsTable,
1813                  "The table for array buffer inner views.");
1814 
1815   ZRREPORT_BYTES(
1816       realmJSPathPrefix + "object-metadata"_ns, realmStats.objectMetadataTable,
1817       "The table used by debugging tools for tracking object metadata");
1818 
1819   ZRREPORT_BYTES(realmJSPathPrefix + "saved-stacks-set"_ns,
1820                  realmStats.savedStacksSet, "The saved stacks set.");
1821 
1822   ZRREPORT_BYTES(realmJSPathPrefix + "non-syntactic-lexical-scopes-table"_ns,
1823                  realmStats.nonSyntacticLexicalScopesTable,
1824                  "The non-syntactic lexical scopes table.");
1825 
1826   ZRREPORT_BYTES(realmJSPathPrefix + "jit-realm"_ns, realmStats.jitRealm,
1827                  "The JIT realm.");
1828 
1829   if (sundriesGCHeap > 0) {
1830     // We deliberately don't use ZRREPORT_GC_BYTES here.
1831     REPORT_GC_BYTES(
1832         realmJSPathPrefix + "sundries/gc-heap"_ns, sundriesGCHeap,
1833         "The sum of all 'gc-heap' measurements that are too small to be "
1834         "worth showing individually.");
1835   }
1836 
1837   if (sundriesMallocHeap > 0) {
1838     // We deliberately don't use ZRREPORT_BYTES here.
1839     REPORT_BYTES(
1840         realmJSPathPrefix + "sundries/malloc-heap"_ns, KIND_HEAP,
1841         sundriesMallocHeap,
1842         "The sum of all 'malloc-heap' measurements that are too small to "
1843         "be worth showing individually.");
1844   }
1845 
1846   if (gcTotalOut) {
1847     *gcTotalOut += gcTotal;
1848   }
1849 }
1850 
ReportScriptSourceStats(const ScriptSourceInfo & scriptSourceInfo,const nsACString & path,nsIHandleReportCallback * handleReport,nsISupports * data,size_t & rtTotal)1851 static void ReportScriptSourceStats(const ScriptSourceInfo& scriptSourceInfo,
1852                                     const nsACString& path,
1853                                     nsIHandleReportCallback* handleReport,
1854                                     nsISupports* data, size_t& rtTotal) {
1855   if (scriptSourceInfo.misc > 0) {
1856     RREPORT_BYTES(path + "misc"_ns, KIND_HEAP, scriptSourceInfo.misc,
1857                   "Miscellaneous data relating to JavaScript source code.");
1858   }
1859 }
1860 
ReportJSRuntimeExplicitTreeStats(const JS::RuntimeStats & rtStats,const nsACString & rtPath,nsIHandleReportCallback * handleReport,nsISupports * data,bool anonymize,size_t * rtTotalOut)1861 void ReportJSRuntimeExplicitTreeStats(const JS::RuntimeStats& rtStats,
1862                                       const nsACString& rtPath,
1863                                       nsIHandleReportCallback* handleReport,
1864                                       nsISupports* data, bool anonymize,
1865                                       size_t* rtTotalOut) {
1866   size_t gcTotal = 0;
1867 
1868   for (const auto& zStats : rtStats.zoneStatsVector) {
1869     const xpc::ZoneStatsExtras* extras =
1870         static_cast<const xpc::ZoneStatsExtras*>(zStats.extra);
1871     ReportZoneStats(zStats, *extras, handleReport, data, anonymize, &gcTotal);
1872   }
1873 
1874   for (const auto& realmStats : rtStats.realmStatsVector) {
1875     const xpc::RealmStatsExtras* extras =
1876         static_cast<const xpc::RealmStatsExtras*>(realmStats.extra);
1877 
1878     ReportRealmStats(realmStats, *extras, handleReport, data, &gcTotal);
1879   }
1880 
1881   // Report the rtStats.runtime numbers under "runtime/", and compute their
1882   // total for later.
1883 
1884   size_t rtTotal = 0;
1885 
1886   RREPORT_BYTES(rtPath + "runtime/runtime-object"_ns, KIND_HEAP,
1887                 rtStats.runtime.object, "The JSRuntime object.");
1888 
1889   RREPORT_BYTES(rtPath + "runtime/atoms-table"_ns, KIND_HEAP,
1890                 rtStats.runtime.atomsTable, "The atoms table.");
1891 
1892   RREPORT_BYTES(rtPath + "runtime/atoms-mark-bitmaps"_ns, KIND_HEAP,
1893                 rtStats.runtime.atomsMarkBitmaps,
1894                 "Mark bitmaps for atoms held by each zone.");
1895 
1896   RREPORT_BYTES(rtPath + "runtime/contexts"_ns, KIND_HEAP,
1897                 rtStats.runtime.contexts,
1898                 "JSContext objects and structures that belong to them.");
1899 
1900   RREPORT_BYTES(
1901       rtPath + "runtime/temporary"_ns, KIND_HEAP, rtStats.runtime.temporary,
1902       "Transient data (mostly parse nodes) held by the JSRuntime during "
1903       "compilation.");
1904 
1905   RREPORT_BYTES(rtPath + "runtime/interpreter-stack"_ns, KIND_HEAP,
1906                 rtStats.runtime.interpreterStack, "JS interpreter frames.");
1907 
1908   RREPORT_BYTES(
1909       rtPath + "runtime/shared-immutable-strings-cache"_ns, KIND_HEAP,
1910       rtStats.runtime.sharedImmutableStringsCache,
1911       "Immutable strings (such as JS scripts' source text) shared across all "
1912       "JSRuntimes.");
1913 
1914   RREPORT_BYTES(rtPath + "runtime/shared-intl-data"_ns, KIND_HEAP,
1915                 rtStats.runtime.sharedIntlData,
1916                 "Shared internationalization data.");
1917 
1918   RREPORT_BYTES(rtPath + "runtime/uncompressed-source-cache"_ns, KIND_HEAP,
1919                 rtStats.runtime.uncompressedSourceCache,
1920                 "The uncompressed source code cache.");
1921 
1922   RREPORT_BYTES(rtPath + "runtime/script-data"_ns, KIND_HEAP,
1923                 rtStats.runtime.scriptData,
1924                 "The table holding script data shared in the runtime.");
1925 
1926   RREPORT_BYTES(rtPath + "runtime/tracelogger"_ns, KIND_HEAP,
1927                 rtStats.runtime.tracelogger,
1928                 "The memory used for the tracelogger (per-runtime).");
1929 
1930   nsCString nonNotablePath =
1931       rtPath +
1932       nsPrintfCString(
1933           "runtime/script-sources/source(scripts=%d, <non-notable files>)/",
1934           rtStats.runtime.scriptSourceInfo.numScripts);
1935 
1936   ReportScriptSourceStats(rtStats.runtime.scriptSourceInfo, nonNotablePath,
1937                           handleReport, data, rtTotal);
1938 
1939   for (size_t i = 0; i < rtStats.runtime.notableScriptSources.length(); i++) {
1940     const JS::NotableScriptSourceInfo& scriptSourceInfo =
1941         rtStats.runtime.notableScriptSources[i];
1942 
1943     // Escape / to \ before we put the filename into the memory reporter
1944     // path, because we don't want any forward slashes in the string to
1945     // count as path separators. Consumers of memory reporters (e.g.
1946     // about:memory) will convert them back to / after doing path
1947     // splitting.
1948     nsCString escapedFilename;
1949     if (anonymize) {
1950       escapedFilename.AppendPrintf("<anonymized-source-%d>", int(i));
1951     } else {
1952       nsDependentCString filename(scriptSourceInfo.filename_.get());
1953       escapedFilename.Append(filename);
1954       escapedFilename.ReplaceSubstring("/", "\\");
1955     }
1956 
1957     nsCString notablePath =
1958         rtPath +
1959         nsPrintfCString("runtime/script-sources/source(scripts=%d, %s)/",
1960                         scriptSourceInfo.numScripts, escapedFilename.get());
1961 
1962     ReportScriptSourceStats(scriptSourceInfo, notablePath, handleReport, data,
1963                             rtTotal);
1964   }
1965 
1966   RREPORT_BYTES(rtPath + "runtime/gc/marker"_ns, KIND_HEAP,
1967                 rtStats.runtime.gc.marker, "The GC mark stack and gray roots.");
1968 
1969   RREPORT_BYTES(rtPath + "runtime/gc/nursery-committed"_ns, KIND_NONHEAP,
1970                 rtStats.runtime.gc.nurseryCommitted,
1971                 "Memory being used by the GC's nursery.");
1972 
1973   RREPORT_BYTES(
1974       rtPath + "runtime/gc/nursery-malloced-buffers"_ns, KIND_HEAP,
1975       rtStats.runtime.gc.nurseryMallocedBuffers,
1976       "Out-of-line slots and elements belonging to objects in the nursery.");
1977 
1978   RREPORT_BYTES(rtPath + "runtime/gc/store-buffer/vals"_ns, KIND_HEAP,
1979                 rtStats.runtime.gc.storeBufferVals,
1980                 "Values in the store buffer.");
1981 
1982   RREPORT_BYTES(rtPath + "runtime/gc/store-buffer/cells"_ns, KIND_HEAP,
1983                 rtStats.runtime.gc.storeBufferCells,
1984                 "Cells in the store buffer.");
1985 
1986   RREPORT_BYTES(rtPath + "runtime/gc/store-buffer/slots"_ns, KIND_HEAP,
1987                 rtStats.runtime.gc.storeBufferSlots,
1988                 "Slots in the store buffer.");
1989 
1990   RREPORT_BYTES(rtPath + "runtime/gc/store-buffer/whole-cells"_ns, KIND_HEAP,
1991                 rtStats.runtime.gc.storeBufferWholeCells,
1992                 "Whole cells in the store buffer.");
1993 
1994   RREPORT_BYTES(rtPath + "runtime/gc/store-buffer/generics"_ns, KIND_HEAP,
1995                 rtStats.runtime.gc.storeBufferGenerics,
1996                 "Generic things in the store buffer.");
1997 
1998   RREPORT_BYTES(rtPath + "runtime/jit-lazylink"_ns, KIND_HEAP,
1999                 rtStats.runtime.jitLazyLink,
2000                 "IonMonkey compilations waiting for lazy linking.");
2001 
2002   if (rtTotalOut) {
2003     *rtTotalOut = rtTotal;
2004   }
2005 
2006   // Report GC numbers that don't belong to a realm.
2007 
2008   // We don't want to report decommitted memory in "explicit", so we just
2009   // change the leading "explicit/" to "decommitted/".
2010   nsCString rtPath2(rtPath);
2011   rtPath2.ReplaceLiteral(0, strlen("explicit"), "decommitted");
2012 
2013   REPORT_GC_BYTES(
2014       rtPath2 + "gc-heap/decommitted-pages"_ns, rtStats.gcHeapDecommittedPages,
2015       "GC arenas in non-empty chunks that is decommitted, i.e. it takes up "
2016       "address space but no physical memory or swap space.");
2017 
2018   REPORT_GC_BYTES(
2019       rtPath + "gc-heap/unused-chunks"_ns, rtStats.gcHeapUnusedChunks,
2020       "Empty GC chunks which will soon be released unless claimed for new "
2021       "allocations.");
2022 
2023   REPORT_GC_BYTES(rtPath + "gc-heap/unused-arenas"_ns,
2024                   rtStats.gcHeapUnusedArenas,
2025                   "Empty GC arenas within non-empty chunks.");
2026 
2027   REPORT_GC_BYTES(rtPath + "gc-heap/chunk-admin"_ns, rtStats.gcHeapChunkAdmin,
2028                   "Bookkeeping information within GC chunks.");
2029 
2030   // gcTotal is the sum of everything we've reported for the GC heap.  It
2031   // should equal rtStats.gcHeapChunkTotal.
2032   MOZ_ASSERT(gcTotal == rtStats.gcHeapChunkTotal);
2033 }
2034 
2035 }  // namespace xpc
2036 
2037 class JSMainRuntimeRealmsReporter final : public nsIMemoryReporter {
2038   ~JSMainRuntimeRealmsReporter() = default;
2039 
2040  public:
2041   NS_DECL_ISUPPORTS
2042 
2043   struct Data {
2044     int anonymizeID;
2045     js::Vector<nsCString, 0, js::SystemAllocPolicy> paths;
2046   };
2047 
RealmCallback(JSContext * cx,void * vdata,Realm * realm,const JS::AutoRequireNoGC & nogc)2048   static void RealmCallback(JSContext* cx, void* vdata, Realm* realm,
2049                             const JS::AutoRequireNoGC& nogc) {
2050     // silently ignore OOM errors
2051     Data* data = static_cast<Data*>(vdata);
2052     nsCString path;
2053     GetRealmName(realm, path, &data->anonymizeID, /* replaceSlashes = */ true);
2054     path.Insert(js::IsSystemRealm(realm) ? "js-main-runtime-realms/system/"_ns
2055                                          : "js-main-runtime-realms/user/"_ns,
2056                 0);
2057     mozilla::Unused << data->paths.append(path);
2058   }
2059 
CollectReports(nsIHandleReportCallback * handleReport,nsISupports * data,bool anonymize)2060   NS_IMETHOD CollectReports(nsIHandleReportCallback* handleReport,
2061                             nsISupports* data, bool anonymize) override {
2062     // First we collect the realm paths.  Then we report them.  Doing
2063     // the two steps interleaved is a bad idea, because calling
2064     // |handleReport| from within RealmCallback() leads to all manner
2065     // of assertions.
2066 
2067     Data d;
2068     d.anonymizeID = anonymize ? 1 : 0;
2069     JS::IterateRealms(XPCJSContext::Get()->Context(), &d, RealmCallback);
2070 
2071     for (auto& path : d.paths) {
2072       REPORT(nsCString(path), KIND_OTHER, UNITS_COUNT, 1,
2073              "A live realm in the main JSRuntime.");
2074     }
2075 
2076     return NS_OK;
2077   }
2078 };
2079 
2080 NS_IMPL_ISUPPORTS(JSMainRuntimeRealmsReporter, nsIMemoryReporter)
2081 
2082 MOZ_DEFINE_MALLOC_SIZE_OF(OrphanMallocSizeOf)
2083 
2084 namespace xpc {
2085 
2086 class OrphanReporter : public JS::ObjectPrivateVisitor {
2087  public:
OrphanReporter(GetISupportsFun aGetISupports)2088   explicit OrphanReporter(GetISupportsFun aGetISupports)
2089       : JS::ObjectPrivateVisitor(aGetISupports), mState(OrphanMallocSizeOf) {}
2090 
sizeOfIncludingThis(nsISupports * aSupports)2091   virtual size_t sizeOfIncludingThis(nsISupports* aSupports) override {
2092     nsCOMPtr<nsINode> node = do_QueryInterface(aSupports);
2093     if (!node || node->IsInComposedDoc()) {
2094       return 0;
2095     }
2096 
2097     // This is an orphan node.  If we haven't already handled the sub-tree that
2098     // this node belongs to, measure the sub-tree's size and then record its
2099     // root so we don't measure it again.
2100     nsCOMPtr<nsINode> orphanTree = node->SubtreeRoot();
2101     if (!orphanTree || mState.HaveSeenPtr(orphanTree.get())) {
2102       return 0;
2103     }
2104 
2105     nsWindowSizes sizes(mState);
2106     mozilla::dom::Document::AddSizeOfNodeTree(*orphanTree, sizes);
2107 
2108     // We combine the node size with nsStyleSizes here. It's not ideal, but it's
2109     // hard to get the style structs measurements out to nsWindowMemoryReporter.
2110     // Also, we drop mServoData in UnbindFromTree(), so in theory any
2111     // non-in-tree element won't have any style data to measure.
2112     //
2113     // FIXME(emilio): We should ideally not do this, since ShadowRoots keep
2114     // their StyleSheets alive even when detached from a document, and those
2115     // could be significant in theory.
2116     return sizes.getTotalSize();
2117   }
2118 
2119  private:
2120   SizeOfState mState;
2121 };
2122 
2123 #ifdef DEBUG
StartsWithExplicit(nsACString & s)2124 static bool StartsWithExplicit(nsACString& s) {
2125   return StringBeginsWith(s, "explicit/"_ns);
2126 }
2127 #endif
2128 
2129 class XPCJSRuntimeStats : public JS::RuntimeStats {
2130   WindowPaths* mWindowPaths;
2131   WindowPaths* mTopWindowPaths;
2132   int mAnonymizeID;
2133 
2134  public:
XPCJSRuntimeStats(WindowPaths * windowPaths,WindowPaths * topWindowPaths,bool anonymize)2135   XPCJSRuntimeStats(WindowPaths* windowPaths, WindowPaths* topWindowPaths,
2136                     bool anonymize)
2137       : JS::RuntimeStats(JSMallocSizeOf),
2138         mWindowPaths(windowPaths),
2139         mTopWindowPaths(topWindowPaths),
2140         mAnonymizeID(anonymize ? 1 : 0) {}
2141 
~XPCJSRuntimeStats()2142   ~XPCJSRuntimeStats() {
2143     for (size_t i = 0; i != realmStatsVector.length(); ++i) {
2144       delete static_cast<xpc::RealmStatsExtras*>(realmStatsVector[i].extra);
2145     }
2146 
2147     for (size_t i = 0; i != zoneStatsVector.length(); ++i) {
2148       delete static_cast<xpc::ZoneStatsExtras*>(zoneStatsVector[i].extra);
2149     }
2150   }
2151 
initExtraZoneStats(JS::Zone * zone,JS::ZoneStats * zStats,const JS::AutoRequireNoGC & nogc)2152   virtual void initExtraZoneStats(JS::Zone* zone, JS::ZoneStats* zStats,
2153                                   const JS::AutoRequireNoGC& nogc) override {
2154     xpc::ZoneStatsExtras* extras = new xpc::ZoneStatsExtras;
2155     extras->pathPrefix.AssignLiteral("explicit/js-non-window/zones/");
2156 
2157     // Get some global in this zone.
2158     Rooted<Realm*> realm(dom::RootingCx(), js::GetAnyRealmInZone(zone));
2159     if (realm) {
2160       RootedObject global(dom::RootingCx(), JS::GetRealmGlobalOrNull(realm));
2161       if (global) {
2162         RefPtr<nsGlobalWindowInner> window;
2163         if (NS_SUCCEEDED(UNWRAP_NON_WRAPPER_OBJECT(Window, global, window))) {
2164           // The global is a |window| object.  Use the path prefix that
2165           // we should have already created for it.
2166           if (mTopWindowPaths->Get(window->WindowID(), &extras->pathPrefix)) {
2167             extras->pathPrefix.AppendLiteral("/js-");
2168           }
2169         }
2170       }
2171     }
2172 
2173     extras->pathPrefix += nsPrintfCString("zone(0x%p)/", (void*)zone);
2174 
2175     MOZ_ASSERT(StartsWithExplicit(extras->pathPrefix));
2176 
2177     zStats->extra = extras;
2178   }
2179 
initExtraRealmStats(Realm * realm,JS::RealmStats * realmStats,const JS::AutoRequireNoGC & nogc)2180   virtual void initExtraRealmStats(Realm* realm, JS::RealmStats* realmStats,
2181                                    const JS::AutoRequireNoGC& nogc) override {
2182     xpc::RealmStatsExtras* extras = new xpc::RealmStatsExtras;
2183     nsCString rName;
2184     GetRealmName(realm, rName, &mAnonymizeID, /* replaceSlashes = */ true);
2185 
2186     // Get the realm's global.
2187     bool needZone = true;
2188     RootedObject global(dom::RootingCx(), JS::GetRealmGlobalOrNull(realm));
2189     if (global) {
2190       RefPtr<nsGlobalWindowInner> window;
2191       if (NS_SUCCEEDED(UNWRAP_NON_WRAPPER_OBJECT(Window, global, window))) {
2192         // The global is a |window| object.  Use the path prefix that
2193         // we should have already created for it.
2194         if (mWindowPaths->Get(window->WindowID(), &extras->jsPathPrefix)) {
2195           extras->domPathPrefix.Assign(extras->jsPathPrefix);
2196           extras->domPathPrefix.AppendLiteral("/dom/");
2197           extras->jsPathPrefix.AppendLiteral("/js-");
2198           needZone = false;
2199         } else {
2200           extras->jsPathPrefix.AssignLiteral("explicit/js-non-window/zones/");
2201           extras->domPathPrefix.AssignLiteral(
2202               "explicit/dom/unknown-window-global?!/");
2203         }
2204       } else {
2205         extras->jsPathPrefix.AssignLiteral("explicit/js-non-window/zones/");
2206         extras->domPathPrefix.AssignLiteral(
2207             "explicit/dom/non-window-global?!/");
2208       }
2209     } else {
2210       extras->jsPathPrefix.AssignLiteral("explicit/js-non-window/zones/");
2211       extras->domPathPrefix.AssignLiteral("explicit/dom/no-global?!/");
2212     }
2213 
2214     if (needZone) {
2215       extras->jsPathPrefix +=
2216           nsPrintfCString("zone(0x%p)/", (void*)js::GetRealmZone(realm));
2217     }
2218 
2219     extras->jsPathPrefix += "realm("_ns + rName + ")/"_ns;
2220 
2221     // extras->jsPathPrefix is used for almost all the realm-specific
2222     // reports. At this point it has the form
2223     // "<something>realm(<rname>)/".
2224     //
2225     // extras->domPathPrefix is used for DOM orphan nodes, which are
2226     // counted by the JS reporter but reported as part of the DOM
2227     // measurements. At this point it has the form "<something>/dom/" if
2228     // this realm belongs to an nsGlobalWindow, and
2229     // "explicit/dom/<something>?!/" otherwise (in which case it shouldn't
2230     // be used, because non-nsGlobalWindow realms shouldn't have
2231     // orphan DOM nodes).
2232 
2233     MOZ_ASSERT(StartsWithExplicit(extras->jsPathPrefix));
2234     MOZ_ASSERT(StartsWithExplicit(extras->domPathPrefix));
2235 
2236     realmStats->extra = extras;
2237   }
2238 };
2239 
CollectReports(WindowPaths * windowPaths,WindowPaths * topWindowPaths,nsIHandleReportCallback * handleReport,nsISupports * data,bool anonymize)2240 void JSReporter::CollectReports(WindowPaths* windowPaths,
2241                                 WindowPaths* topWindowPaths,
2242                                 nsIHandleReportCallback* handleReport,
2243                                 nsISupports* data, bool anonymize) {
2244   XPCJSRuntime* xpcrt = nsXPConnect::GetRuntimeInstance();
2245 
2246   // In the first step we get all the stats and stash them in a local
2247   // data structure.  In the second step we pass all the stashed stats to
2248   // the callback.  Separating these steps is important because the
2249   // callback may be a JS function, and executing JS while getting these
2250   // stats seems like a bad idea.
2251 
2252   XPCJSRuntimeStats rtStats(windowPaths, topWindowPaths, anonymize);
2253   OrphanReporter orphanReporter(XPCConvert::GetISupportsFromJSObject);
2254   JSContext* cx = XPCJSContext::Get()->Context();
2255   if (!JS::CollectRuntimeStats(cx, &rtStats, &orphanReporter, anonymize)) {
2256     return;
2257   }
2258 
2259   // Collect JS stats not associated with a Runtime such as helper threads or
2260   // global tracelogger data. We do this here in JSReporter::CollectReports
2261   // as this is used for the main Runtime in process.
2262   JS::GlobalStats gStats(JSMallocSizeOf);
2263   if (!JS::CollectGlobalStats(&gStats)) {
2264     return;
2265   }
2266 
2267   size_t xpcJSRuntimeSize = xpcrt->SizeOfIncludingThis(JSMallocSizeOf);
2268 
2269   size_t wrappedJSSize =
2270       xpcrt->GetMultiCompartmentWrappedJSMap()->SizeOfWrappedJS(JSMallocSizeOf);
2271 
2272   XPCWrappedNativeScope::ScopeSizeInfo sizeInfo(JSMallocSizeOf);
2273   XPCWrappedNativeScope::AddSizeOfAllScopesIncludingThis(cx, &sizeInfo);
2274 
2275   mozJSComponentLoader* loader = mozJSComponentLoader::Get();
2276   size_t jsComponentLoaderSize =
2277       loader ? loader->SizeOfIncludingThis(JSMallocSizeOf) : 0;
2278 
2279   // This is the second step (see above).  First we report stuff in the
2280   // "explicit" tree, then we report other stuff.
2281 
2282   size_t rtTotal = 0;
2283   xpc::ReportJSRuntimeExplicitTreeStats(rtStats, "explicit/js-non-window/"_ns,
2284                                         handleReport, data, anonymize,
2285                                         &rtTotal);
2286 
2287   // Report the sums of the realm numbers.
2288   xpc::RealmStatsExtras realmExtrasTotal;
2289   realmExtrasTotal.jsPathPrefix.AssignLiteral("js-main-runtime/realms/");
2290   realmExtrasTotal.domPathPrefix.AssignLiteral("window-objects/dom/");
2291   ReportRealmStats(rtStats.realmTotals, realmExtrasTotal, handleReport, data);
2292 
2293   xpc::ZoneStatsExtras zExtrasTotal;
2294   zExtrasTotal.pathPrefix.AssignLiteral("js-main-runtime/zones/");
2295   ReportZoneStats(rtStats.zTotals, zExtrasTotal, handleReport, data, anonymize);
2296 
2297   // Report the sum of the runtime/ numbers.
2298   REPORT_BYTES(
2299       "js-main-runtime/runtime"_ns, KIND_OTHER, rtTotal,
2300       "The sum of all measurements under 'explicit/js-non-window/runtime/'.");
2301 
2302   // Report the number of HelperThread
2303 
2304   REPORT("js-helper-threads/idle"_ns, KIND_OTHER, UNITS_COUNT,
2305          gStats.helperThread.idleThreadCount,
2306          "The current number of idle JS HelperThreads.");
2307 
2308   REPORT(
2309       "js-helper-threads/active"_ns, KIND_OTHER, UNITS_COUNT,
2310       gStats.helperThread.activeThreadCount,
2311       "The current number of active JS HelperThreads. Memory held by these is"
2312       " not reported.");
2313 
2314   // Report the numbers for memory used by wasm Runtime state.
2315   REPORT_BYTES("wasm-runtime"_ns, KIND_OTHER, rtStats.runtime.wasmRuntime,
2316                "The memory used for wasm runtime bookkeeping.");
2317 
2318   // Report the numbers for memory outside of realms.
2319 
2320   REPORT_BYTES("js-main-runtime/gc-heap/unused-chunks"_ns, KIND_OTHER,
2321                rtStats.gcHeapUnusedChunks,
2322                "The same as 'explicit/js-non-window/gc-heap/unused-chunks'.");
2323 
2324   REPORT_BYTES("js-main-runtime/gc-heap/unused-arenas"_ns, KIND_OTHER,
2325                rtStats.gcHeapUnusedArenas,
2326                "The same as 'explicit/js-non-window/gc-heap/unused-arenas'.");
2327 
2328   REPORT_BYTES("js-main-runtime/gc-heap/chunk-admin"_ns, KIND_OTHER,
2329                rtStats.gcHeapChunkAdmin,
2330                "The same as 'explicit/js-non-window/gc-heap/chunk-admin'.");
2331 
2332   // Report a breakdown of the committed GC space.
2333 
2334   REPORT_BYTES("js-main-runtime-gc-heap-committed/unused/chunks"_ns, KIND_OTHER,
2335                rtStats.gcHeapUnusedChunks,
2336                "The same as 'explicit/js-non-window/gc-heap/unused-chunks'.");
2337 
2338   REPORT_BYTES("js-main-runtime-gc-heap-committed/unused/arenas"_ns, KIND_OTHER,
2339                rtStats.gcHeapUnusedArenas,
2340                "The same as 'explicit/js-non-window/gc-heap/unused-arenas'.");
2341 
2342   REPORT_BYTES(
2343       nsLiteralCString(
2344           "js-main-runtime-gc-heap-committed/unused/gc-things/objects"),
2345       KIND_OTHER, rtStats.zTotals.unusedGCThings.object,
2346       "Unused object cells within non-empty arenas.");
2347 
2348   REPORT_BYTES(
2349       nsLiteralCString(
2350           "js-main-runtime-gc-heap-committed/unused/gc-things/strings"),
2351       KIND_OTHER, rtStats.zTotals.unusedGCThings.string,
2352       "Unused string cells within non-empty arenas.");
2353 
2354   REPORT_BYTES(
2355       nsLiteralCString(
2356           "js-main-runtime-gc-heap-committed/unused/gc-things/symbols"),
2357       KIND_OTHER, rtStats.zTotals.unusedGCThings.symbol,
2358       "Unused symbol cells within non-empty arenas.");
2359 
2360   REPORT_BYTES(nsLiteralCString(
2361                    "js-main-runtime-gc-heap-committed/unused/gc-things/shapes"),
2362                KIND_OTHER, rtStats.zTotals.unusedGCThings.shape,
2363                "Unused shape cells within non-empty arenas.");
2364 
2365   REPORT_BYTES(
2366       nsLiteralCString(
2367           "js-main-runtime-gc-heap-committed/unused/gc-things/base-shapes"),
2368       KIND_OTHER, rtStats.zTotals.unusedGCThings.baseShape,
2369       "Unused base shape cells within non-empty arenas.");
2370 
2371   REPORT_BYTES(
2372       nsLiteralCString(
2373           "js-main-runtime-gc-heap-committed/unused/gc-things/getter-setters"),
2374       KIND_OTHER, rtStats.zTotals.unusedGCThings.getterSetter,
2375       "Unused getter-setter cells within non-empty arenas.");
2376 
2377   REPORT_BYTES(
2378       nsLiteralCString(
2379           "js-main-runtime-gc-heap-committed/unused/gc-things/property-maps"),
2380       KIND_OTHER, rtStats.zTotals.unusedGCThings.propMap,
2381       "Unused property map cells within non-empty arenas.");
2382 
2383   REPORT_BYTES(nsLiteralCString(
2384                    "js-main-runtime-gc-heap-committed/unused/gc-things/scopes"),
2385                KIND_OTHER, rtStats.zTotals.unusedGCThings.scope,
2386                "Unused scope cells within non-empty arenas.");
2387 
2388   REPORT_BYTES(
2389       nsLiteralCString(
2390           "js-main-runtime-gc-heap-committed/unused/gc-things/scripts"),
2391       KIND_OTHER, rtStats.zTotals.unusedGCThings.script,
2392       "Unused script cells within non-empty arenas.");
2393 
2394   REPORT_BYTES(
2395       nsLiteralCString(
2396           "js-main-runtime-gc-heap-committed/unused/gc-things/jitcode"),
2397       KIND_OTHER, rtStats.zTotals.unusedGCThings.jitcode,
2398       "Unused jitcode cells within non-empty arenas.");
2399 
2400   REPORT_BYTES(
2401       nsLiteralCString(
2402           "js-main-runtime-gc-heap-committed/unused/gc-things/regexp-shareds"),
2403       KIND_OTHER, rtStats.zTotals.unusedGCThings.regExpShared,
2404       "Unused regexpshared cells within non-empty arenas.");
2405 
2406   REPORT_BYTES("js-main-runtime-gc-heap-committed/used/chunk-admin"_ns,
2407                KIND_OTHER, rtStats.gcHeapChunkAdmin,
2408                "The same as 'explicit/js-non-window/gc-heap/chunk-admin'.");
2409 
2410   REPORT_BYTES("js-main-runtime-gc-heap-committed/used/arena-admin"_ns,
2411                KIND_OTHER, rtStats.zTotals.gcHeapArenaAdmin,
2412                "The same as 'js-main-runtime/zones/gc-heap-arena-admin'.");
2413 
2414   size_t gcThingTotal = 0;
2415 
2416   MREPORT_BYTES(nsLiteralCString(
2417                     "js-main-runtime-gc-heap-committed/used/gc-things/objects"),
2418                 KIND_OTHER, rtStats.realmTotals.classInfo.objectsGCHeap,
2419                 "Used object cells.");
2420 
2421   MREPORT_BYTES(nsLiteralCString(
2422                     "js-main-runtime-gc-heap-committed/used/gc-things/strings"),
2423                 KIND_OTHER, rtStats.zTotals.stringInfo.sizeOfLiveGCThings(),
2424                 "Used string cells.");
2425 
2426   MREPORT_BYTES(nsLiteralCString(
2427                     "js-main-runtime-gc-heap-committed/used/gc-things/symbols"),
2428                 KIND_OTHER, rtStats.zTotals.symbolsGCHeap,
2429                 "Used symbol cells.");
2430 
2431   MREPORT_BYTES(nsLiteralCString(
2432                     "js-main-runtime-gc-heap-committed/used/gc-things/shapes"),
2433                 KIND_OTHER,
2434                 rtStats.zTotals.shapeInfo.shapesGCHeapShared +
2435                     rtStats.zTotals.shapeInfo.shapesGCHeapDict,
2436                 "Used shape cells.");
2437 
2438   MREPORT_BYTES(
2439       nsLiteralCString(
2440           "js-main-runtime-gc-heap-committed/used/gc-things/base-shapes"),
2441       KIND_OTHER, rtStats.zTotals.shapeInfo.shapesGCHeapBase,
2442       "Used base shape cells.");
2443 
2444   MREPORT_BYTES(
2445       nsLiteralCString(
2446           "js-main-runtime-gc-heap-committed/used/gc-things/getter-setters"),
2447       KIND_OTHER, rtStats.zTotals.getterSettersGCHeap,
2448       "Used getter/setter cells.");
2449 
2450   MREPORT_BYTES(
2451       nsLiteralCString(
2452           "js-main-runtime-gc-heap-committed/used/gc-things/property-maps"),
2453       KIND_OTHER,
2454       rtStats.zTotals.dictPropMapsGCHeap +
2455           rtStats.zTotals.compactPropMapsGCHeap +
2456           rtStats.zTotals.normalPropMapsGCHeap,
2457       "Used property map cells.");
2458 
2459   MREPORT_BYTES(nsLiteralCString(
2460                     "js-main-runtime-gc-heap-committed/used/gc-things/scopes"),
2461                 KIND_OTHER, rtStats.zTotals.scopesGCHeap, "Used scope cells.");
2462 
2463   MREPORT_BYTES(nsLiteralCString(
2464                     "js-main-runtime-gc-heap-committed/used/gc-things/scripts"),
2465                 KIND_OTHER, rtStats.realmTotals.scriptsGCHeap,
2466                 "Used script cells.");
2467 
2468   MREPORT_BYTES(nsLiteralCString(
2469                     "js-main-runtime-gc-heap-committed/used/gc-things/jitcode"),
2470                 KIND_OTHER, rtStats.zTotals.jitCodesGCHeap,
2471                 "Used jitcode cells.");
2472 
2473   MREPORT_BYTES(
2474       nsLiteralCString(
2475           "js-main-runtime-gc-heap-committed/used/gc-things/regexp-shareds"),
2476       KIND_OTHER, rtStats.zTotals.regExpSharedsGCHeap,
2477       "Used regexpshared cells.");
2478 
2479   MOZ_ASSERT(gcThingTotal == rtStats.gcHeapGCThings);
2480 
2481   // Report xpconnect.
2482 
2483   REPORT_BYTES("explicit/xpconnect/runtime"_ns, KIND_HEAP, xpcJSRuntimeSize,
2484                "The XPConnect runtime.");
2485 
2486   REPORT_BYTES("explicit/xpconnect/wrappedjs"_ns, KIND_HEAP, wrappedJSSize,
2487                "Wrappers used to implement XPIDL interfaces with JS.");
2488 
2489   REPORT_BYTES("explicit/xpconnect/scopes"_ns, KIND_HEAP,
2490                sizeInfo.mScopeAndMapSize, "XPConnect scopes.");
2491 
2492   REPORT_BYTES("explicit/xpconnect/proto-iface-cache"_ns, KIND_HEAP,
2493                sizeInfo.mProtoAndIfaceCacheSize,
2494                "Prototype and interface binding caches.");
2495 
2496   REPORT_BYTES("explicit/xpconnect/js-component-loader"_ns, KIND_HEAP,
2497                jsComponentLoaderSize, "XPConnect's JS component loader.");
2498 
2499   // Report tracelogger (global).
2500 
2501   REPORT_BYTES(
2502       "explicit/js-non-window/tracelogger"_ns, KIND_HEAP, gStats.tracelogger,
2503       "The memory used for the tracelogger, including the graph and events.");
2504 
2505   // Report HelperThreadState.
2506 
2507   REPORT_BYTES("explicit/js-non-window/helper-thread/heap-other"_ns, KIND_HEAP,
2508                gStats.helperThread.stateData,
2509                "Memory used by HelperThreadState.");
2510 
2511   REPORT_BYTES("explicit/js-non-window/helper-thread/parse-task"_ns, KIND_HEAP,
2512                gStats.helperThread.parseTask,
2513                "The memory used by ParseTasks waiting in HelperThreadState.");
2514 
2515   REPORT_BYTES(
2516       "explicit/js-non-window/helper-thread/ion-compile-task"_ns, KIND_HEAP,
2517       gStats.helperThread.ionCompileTask,
2518       "The memory used by IonCompileTasks waiting in HelperThreadState.");
2519 
2520   REPORT_BYTES(
2521       "explicit/js-non-window/helper-thread/wasm-compile"_ns, KIND_HEAP,
2522       gStats.helperThread.wasmCompile,
2523       "The memory used by Wasm compilations waiting in HelperThreadState.");
2524 
2525   REPORT_BYTES("explicit/js-non-window/helper-thread/contexts"_ns, KIND_HEAP,
2526                gStats.helperThread.contexts,
2527                "The memory used by the JSContexts in HelperThreadState.");
2528 }
2529 
JSSizeOfTab(JSObject * objArg,size_t * jsObjectsSize,size_t * jsStringsSize,size_t * jsPrivateSize,size_t * jsOtherSize)2530 static nsresult JSSizeOfTab(JSObject* objArg, size_t* jsObjectsSize,
2531                             size_t* jsStringsSize, size_t* jsPrivateSize,
2532                             size_t* jsOtherSize) {
2533   JSContext* cx = XPCJSContext::Get()->Context();
2534   JS::RootedObject obj(cx, objArg);
2535 
2536   TabSizes sizes;
2537   OrphanReporter orphanReporter(XPCConvert::GetISupportsFromJSObject);
2538   NS_ENSURE_TRUE(
2539       JS::AddSizeOfTab(cx, obj, moz_malloc_size_of, &orphanReporter, &sizes),
2540       NS_ERROR_OUT_OF_MEMORY);
2541 
2542   *jsObjectsSize = sizes.objects_;
2543   *jsStringsSize = sizes.strings_;
2544   *jsPrivateSize = sizes.private_;
2545   *jsOtherSize = sizes.other_;
2546   return NS_OK;
2547 }
2548 
2549 }  // namespace xpc
2550 
AccumulateTelemetryCallback(int id,uint32_t sample,const char * key)2551 static void AccumulateTelemetryCallback(int id, uint32_t sample,
2552                                         const char* key) {
2553   switch (id) {
2554     case JS_TELEMETRY_GC_REASON:
2555       Telemetry::Accumulate(Telemetry::GC_REASON_2, sample);
2556       break;
2557     case JS_TELEMETRY_GC_IS_ZONE_GC:
2558       Telemetry::Accumulate(Telemetry::GC_IS_COMPARTMENTAL, sample);
2559       break;
2560     case JS_TELEMETRY_GC_MS:
2561       Telemetry::Accumulate(Telemetry::GC_MS, sample);
2562       break;
2563     case JS_TELEMETRY_GC_BUDGET_MS_2:
2564       Telemetry::Accumulate(Telemetry::GC_BUDGET_MS_2, sample);
2565       break;
2566     case JS_TELEMETRY_GC_BUDGET_OVERRUN:
2567       Telemetry::Accumulate(Telemetry::GC_BUDGET_OVERRUN, sample);
2568       break;
2569     case JS_TELEMETRY_GC_ANIMATION_MS:
2570       Telemetry::Accumulate(Telemetry::GC_ANIMATION_MS, sample);
2571       break;
2572     case JS_TELEMETRY_GC_MAX_PAUSE_MS_2:
2573       Telemetry::Accumulate(Telemetry::GC_MAX_PAUSE_MS_2, sample);
2574       break;
2575     case JS_TELEMETRY_GC_PREPARE_MS:
2576       Telemetry::Accumulate(Telemetry::GC_PREPARE_MS, sample);
2577       break;
2578     case JS_TELEMETRY_GC_MARK_MS:
2579       Telemetry::Accumulate(Telemetry::GC_MARK_MS, sample);
2580       break;
2581     case JS_TELEMETRY_GC_SWEEP_MS:
2582       Telemetry::Accumulate(Telemetry::GC_SWEEP_MS, sample);
2583       break;
2584     case JS_TELEMETRY_GC_COMPACT_MS:
2585       Telemetry::Accumulate(Telemetry::GC_COMPACT_MS, sample);
2586       break;
2587     case JS_TELEMETRY_GC_MARK_ROOTS_US:
2588       Telemetry::Accumulate(Telemetry::GC_MARK_ROOTS_US, sample);
2589       break;
2590     case JS_TELEMETRY_GC_MARK_GRAY_MS_2:
2591       Telemetry::Accumulate(Telemetry::GC_MARK_GRAY_MS_2, sample);
2592       break;
2593     case JS_TELEMETRY_GC_MARK_WEAK_MS:
2594       Telemetry::Accumulate(Telemetry::GC_MARK_WEAK_MS, sample);
2595       break;
2596     case JS_TELEMETRY_GC_SLICE_MS:
2597       Telemetry::Accumulate(Telemetry::GC_SLICE_MS, sample);
2598       break;
2599     case JS_TELEMETRY_GC_SLOW_PHASE:
2600       Telemetry::Accumulate(Telemetry::GC_SLOW_PHASE, sample);
2601       break;
2602     case JS_TELEMETRY_GC_SLOW_TASK:
2603       Telemetry::Accumulate(Telemetry::GC_SLOW_TASK, sample);
2604       break;
2605     case JS_TELEMETRY_GC_MMU_50:
2606       Telemetry::Accumulate(Telemetry::GC_MMU_50, sample);
2607       break;
2608     case JS_TELEMETRY_GC_RESET:
2609       Telemetry::Accumulate(Telemetry::GC_RESET, sample);
2610       break;
2611     case JS_TELEMETRY_GC_RESET_REASON:
2612       Telemetry::Accumulate(Telemetry::GC_RESET_REASON, sample);
2613       break;
2614     case JS_TELEMETRY_GC_NON_INCREMENTAL:
2615       Telemetry::Accumulate(Telemetry::GC_NON_INCREMENTAL, sample);
2616       break;
2617     case JS_TELEMETRY_GC_NON_INCREMENTAL_REASON:
2618       Telemetry::Accumulate(Telemetry::GC_NON_INCREMENTAL_REASON, sample);
2619       break;
2620     case JS_TELEMETRY_GC_MINOR_REASON:
2621       Telemetry::Accumulate(Telemetry::GC_MINOR_REASON, sample);
2622       break;
2623     case JS_TELEMETRY_GC_MINOR_REASON_LONG:
2624       Telemetry::Accumulate(Telemetry::GC_MINOR_REASON_LONG, sample);
2625       break;
2626     case JS_TELEMETRY_GC_MINOR_US:
2627       Telemetry::Accumulate(Telemetry::GC_MINOR_US, sample);
2628       break;
2629     case JS_TELEMETRY_GC_NURSERY_BYTES:
2630       Telemetry::Accumulate(Telemetry::GC_NURSERY_BYTES_2, sample);
2631       break;
2632     case JS_TELEMETRY_GC_PRETENURE_COUNT_2:
2633       Telemetry::Accumulate(Telemetry::GC_PRETENURE_COUNT_2, sample);
2634       break;
2635     case JS_TELEMETRY_GC_NURSERY_PROMOTION_RATE:
2636       Telemetry::Accumulate(Telemetry::GC_NURSERY_PROMOTION_RATE, sample);
2637       break;
2638     case JS_TELEMETRY_GC_TENURED_SURVIVAL_RATE:
2639       Telemetry::Accumulate(Telemetry::GC_TENURED_SURVIVAL_RATE, sample);
2640       break;
2641     case JS_TELEMETRY_GC_MARK_RATE_2:
2642       Telemetry::Accumulate(Telemetry::GC_MARK_RATE_2, sample);
2643       break;
2644     case JS_TELEMETRY_GC_TIME_BETWEEN_S:
2645       Telemetry::Accumulate(Telemetry::GC_TIME_BETWEEN_S, sample);
2646       break;
2647     case JS_TELEMETRY_GC_TIME_BETWEEN_SLICES_MS:
2648       Telemetry::Accumulate(Telemetry::GC_TIME_BETWEEN_SLICES_MS, sample);
2649       break;
2650     case JS_TELEMETRY_GC_SLICE_COUNT:
2651       Telemetry::Accumulate(Telemetry::GC_SLICE_COUNT, sample);
2652       break;
2653     case JS_TELEMETRY_GC_EFFECTIVENESS:
2654       Telemetry::Accumulate(Telemetry::GC_EFFECTIVENESS, sample);
2655       break;
2656     case JS_TELEMETRY_DESERIALIZE_BYTES:
2657       Telemetry::Accumulate(Telemetry::DESERIALIZE_BYTES, sample);
2658       break;
2659     case JS_TELEMETRY_DESERIALIZE_ITEMS:
2660       Telemetry::Accumulate(Telemetry::DESERIALIZE_ITEMS, sample);
2661       break;
2662     case JS_TELEMETRY_DESERIALIZE_US:
2663       Telemetry::Accumulate(Telemetry::DESERIALIZE_US, sample);
2664       break;
2665     default:
2666       // Some telemetry only exists in the JS Shell, and are not reported here.
2667       break;
2668   }
2669 }
2670 
SetUseCounterCallback(JSObject * obj,JSUseCounter counter)2671 static void SetUseCounterCallback(JSObject* obj, JSUseCounter counter) {
2672   switch (counter) {
2673     case JSUseCounter::ASMJS:
2674       SetUseCounter(obj, eUseCounter_custom_JS_asmjs);
2675       break;
2676     case JSUseCounter::WASM:
2677       SetUseCounter(obj, eUseCounter_custom_JS_wasm);
2678       break;
2679     case JSUseCounter::WASM_DUPLICATE_IMPORTS:
2680       SetUseCounter(obj, eUseCounter_custom_JS_wasm_duplicate_imports);
2681       break;
2682     default:
2683       MOZ_ASSERT_UNREACHABLE("Unexpected JSUseCounter id");
2684   }
2685 }
2686 
GetRealmNameCallback(JSContext * cx,Realm * realm,char * buf,size_t bufsize,const JS::AutoRequireNoGC & nogc)2687 static void GetRealmNameCallback(JSContext* cx, Realm* realm, char* buf,
2688                                  size_t bufsize,
2689                                  const JS::AutoRequireNoGC& nogc) {
2690   nsCString name;
2691   // This is called via the JSAPI and isn't involved in memory reporting, so
2692   // we don't need to anonymize realm names.
2693   int anonymizeID = 0;
2694   GetRealmName(realm, name, &anonymizeID, /* replaceSlashes = */ false);
2695   if (name.Length() >= bufsize) {
2696     name.Truncate(bufsize - 1);
2697   }
2698   memcpy(buf, name.get(), name.Length() + 1);
2699 }
2700 
DestroyRealm(JSFreeOp * fop,JS::Realm * realm)2701 static void DestroyRealm(JSFreeOp* fop, JS::Realm* realm) {
2702   // Get the current compartment private into an AutoPtr (which will do the
2703   // cleanup for us), and null out the private field.
2704   mozilla::UniquePtr<RealmPrivate> priv(RealmPrivate::Get(realm));
2705   JS::SetRealmPrivate(realm, nullptr);
2706 }
2707 
PreserveWrapper(JSContext * cx,JS::Handle<JSObject * > obj)2708 static bool PreserveWrapper(JSContext* cx, JS::Handle<JSObject*> obj) {
2709   MOZ_ASSERT(cx);
2710   MOZ_ASSERT(obj);
2711   MOZ_ASSERT(mozilla::dom::IsDOMObject(obj));
2712 
2713   if (!mozilla::dom::TryPreserveWrapper(obj)) {
2714     return false;
2715   }
2716 
2717   MOZ_ASSERT(!mozilla::dom::HasReleasedWrapper(obj),
2718              "There should be no released wrapper since we just preserved it");
2719 
2720   return true;
2721 }
2722 
ReadSourceFromFilename(JSContext * cx,const char * filename,char16_t ** twoByteSource,char ** utf8Source,size_t * len)2723 static nsresult ReadSourceFromFilename(JSContext* cx, const char* filename,
2724                                        char16_t** twoByteSource,
2725                                        char** utf8Source, size_t* len) {
2726   MOZ_ASSERT(*len == 0);
2727   MOZ_ASSERT((twoByteSource != nullptr) != (utf8Source != nullptr),
2728              "must be called requesting only one of UTF-8 or UTF-16 source");
2729   MOZ_ASSERT_IF(twoByteSource, !*twoByteSource);
2730   MOZ_ASSERT_IF(utf8Source, !*utf8Source);
2731 
2732   nsresult rv;
2733 
2734   // mozJSSubScriptLoader prefixes the filenames of the scripts it loads with
2735   // the filename of its caller. Axe that if present.
2736   const char* arrow;
2737   while ((arrow = strstr(filename, " -> "))) {
2738     filename = arrow + strlen(" -> ");
2739   }
2740 
2741   // Get the URI.
2742   nsCOMPtr<nsIURI> uri;
2743   rv = NS_NewURI(getter_AddRefs(uri), filename);
2744   NS_ENSURE_SUCCESS(rv, rv);
2745 
2746   nsCOMPtr<nsIChannel> scriptChannel;
2747   rv = NS_NewChannel(getter_AddRefs(scriptChannel), uri,
2748                      nsContentUtils::GetSystemPrincipal(),
2749                      nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
2750                      nsIContentPolicy::TYPE_OTHER);
2751   NS_ENSURE_SUCCESS(rv, rv);
2752 
2753   // Only allow local reading.
2754   nsCOMPtr<nsIURI> actualUri;
2755   rv = scriptChannel->GetURI(getter_AddRefs(actualUri));
2756   NS_ENSURE_SUCCESS(rv, rv);
2757   nsCString scheme;
2758   rv = actualUri->GetScheme(scheme);
2759   NS_ENSURE_SUCCESS(rv, rv);
2760   if (!scheme.EqualsLiteral("file") && !scheme.EqualsLiteral("jar")) {
2761     return NS_OK;
2762   }
2763 
2764   // Explicitly set the content type so that we don't load the
2765   // exthandler to guess it.
2766   scriptChannel->SetContentType("text/plain"_ns);
2767 
2768   nsCOMPtr<nsIInputStream> scriptStream;
2769   rv = scriptChannel->Open(getter_AddRefs(scriptStream));
2770   NS_ENSURE_SUCCESS(rv, rv);
2771 
2772   uint64_t rawLen;
2773   rv = scriptStream->Available(&rawLen);
2774   NS_ENSURE_SUCCESS(rv, rv);
2775   if (!rawLen) {
2776     return NS_ERROR_FAILURE;
2777   }
2778 
2779   // Technically, this should be SIZE_MAX, but we don't run on machines
2780   // where that would be less than UINT32_MAX, and the latter is already
2781   // well beyond a reasonable limit.
2782   if (rawLen > UINT32_MAX) {
2783     return NS_ERROR_FILE_TOO_BIG;
2784   }
2785 
2786   // Allocate a buffer the size of the file to initially fill with the UTF-8
2787   // contents of the file.  Use the JS allocator so that if UTF-8 source was
2788   // requested, we can return this memory directly.
2789   JS::UniqueChars buf(js_pod_malloc<char>(rawLen));
2790   if (!buf) {
2791     return NS_ERROR_OUT_OF_MEMORY;
2792   }
2793 
2794   char* ptr = buf.get();
2795   char* end = ptr + rawLen;
2796   while (ptr < end) {
2797     uint32_t bytesRead;
2798     rv = scriptStream->Read(ptr, PointerRangeSize(ptr, end), &bytesRead);
2799     if (NS_FAILED(rv)) {
2800       return rv;
2801     }
2802     MOZ_ASSERT(bytesRead > 0, "stream promised more bytes before EOF");
2803     ptr += bytesRead;
2804   }
2805 
2806   if (utf8Source) {
2807     // |buf| is already UTF-8, so we can directly return it.
2808     *len = rawLen;
2809     *utf8Source = buf.release();
2810   } else {
2811     MOZ_ASSERT(twoByteSource != nullptr);
2812 
2813     // |buf| can't be directly returned -- convert it to UTF-16.
2814 
2815     // On success this overwrites |*twoByteSource| and |*len|.
2816     rv = ScriptLoader::ConvertToUTF16(
2817         scriptChannel, reinterpret_cast<const unsigned char*>(buf.get()),
2818         rawLen, u"UTF-8"_ns, nullptr, *twoByteSource, *len);
2819     NS_ENSURE_SUCCESS(rv, rv);
2820 
2821     if (!*twoByteSource) {
2822       return NS_ERROR_FAILURE;
2823     }
2824   }
2825 
2826   return NS_OK;
2827 }
2828 
2829 // The JS engine calls this object's 'load' member function when it needs
2830 // the source for a chrome JS function. See the comment in the XPCJSRuntime
2831 // constructor.
2832 class XPCJSSourceHook : public js::SourceHook {
load(JSContext * cx,const char * filename,char16_t ** twoByteSource,char ** utf8Source,size_t * length)2833   bool load(JSContext* cx, const char* filename, char16_t** twoByteSource,
2834             char** utf8Source, size_t* length) override {
2835     MOZ_ASSERT((twoByteSource != nullptr) != (utf8Source != nullptr),
2836                "must be called requesting only one of UTF-8 or UTF-16 source");
2837 
2838     *length = 0;
2839     if (twoByteSource) {
2840       *twoByteSource = nullptr;
2841     } else {
2842       *utf8Source = nullptr;
2843     }
2844 
2845     if (!nsContentUtils::IsSystemCaller(cx)) {
2846       return true;
2847     }
2848 
2849     if (!filename) {
2850       return true;
2851     }
2852 
2853     nsresult rv =
2854         ReadSourceFromFilename(cx, filename, twoByteSource, utf8Source, length);
2855     if (NS_FAILED(rv)) {
2856       xpc::Throw(cx, rv);
2857       return false;
2858     }
2859 
2860     return true;
2861   }
2862 };
2863 
2864 static const JSWrapObjectCallbacks WrapObjectCallbacks = {
2865     xpc::WrapperFactory::Rewrap, xpc::WrapperFactory::PrepareForWrapping};
2866 
XPCJSRuntime(JSContext * aCx)2867 XPCJSRuntime::XPCJSRuntime(JSContext* aCx)
2868     : CycleCollectedJSRuntime(aCx),
2869       mWrappedJSMap(mozilla::MakeUnique<JSObject2WrappedJSMap>()),
2870       mIID2NativeInterfaceMap(mozilla::MakeUnique<IID2NativeInterfaceMap>()),
2871       mClassInfo2NativeSetMap(mozilla::MakeUnique<ClassInfo2NativeSetMap>()),
2872       mNativeSetMap(mozilla::MakeUnique<NativeSetMap>()),
2873       mWrappedNativeScopes(),
2874       mDyingWrappedNativeProtoMap(
2875           mozilla::MakeUnique<XPCWrappedNativeProtoMap>()),
2876       mGCIsRunning(false),
2877       mNativesToReleaseArray(),
2878       mDoingFinalization(false),
2879       mVariantRoots(nullptr),
2880       mWrappedJSRoots(nullptr),
2881       mAsyncSnowWhiteFreer(new AsyncFreeSnowWhite()) {
2882   MOZ_COUNT_CTOR_INHERITED(XPCJSRuntime, CycleCollectedJSRuntime);
2883 }
2884 
2885 /* static */
Get()2886 XPCJSRuntime* XPCJSRuntime::Get() { return nsXPConnect::GetRuntimeInstance(); }
2887 
2888 // Subclass of JS::ubi::Base for DOM reflector objects for the JS::ubi::Node
2889 // memory analysis framework; see js/public/UbiNode.h. In
2890 // XPCJSRuntime::Initialize, we register the ConstructUbiNode function as a hook
2891 // with the SpiderMonkey runtime for it to use to construct ubi::Nodes of this
2892 // class for JSObjects whose class has the JSCLASS_IS_DOMJSCLASS flag set.
2893 // ReflectorNode specializes Concrete<JSObject> for DOM reflector nodes,
2894 // reporting the edge from the JSObject to the nsINode it represents, in
2895 // addition to the usual edges departing any normal JSObject.
2896 namespace JS {
2897 namespace ubi {
2898 class ReflectorNode : public Concrete<JSObject> {
2899  protected:
ReflectorNode(JSObject * ptr)2900   explicit ReflectorNode(JSObject* ptr) : Concrete<JSObject>(ptr) {}
2901 
2902  public:
construct(void * storage,JSObject * ptr)2903   static void construct(void* storage, JSObject* ptr) {
2904     new (storage) ReflectorNode(ptr);
2905   }
2906   js::UniquePtr<JS::ubi::EdgeRange> edges(JSContext* cx,
2907                                           bool wantNames) const override;
2908 };
2909 
edges(JSContext * cx,bool wantNames) const2910 js::UniquePtr<EdgeRange> ReflectorNode::edges(JSContext* cx,
2911                                               bool wantNames) const {
2912   js::UniquePtr<SimpleEdgeRange> range(static_cast<SimpleEdgeRange*>(
2913       Concrete<JSObject>::edges(cx, wantNames).release()));
2914   if (!range) {
2915     return nullptr;
2916   }
2917   // UNWRAP_NON_WRAPPER_OBJECT assumes the object is completely initialized,
2918   // but ours may not be. Luckily, UnwrapDOMObjectToISupports checks for the
2919   // uninitialized case (and returns null if uninitialized), so we can use that
2920   // to guard against uninitialized objects.
2921   nsISupports* supp = UnwrapDOMObjectToISupports(&get());
2922   if (supp) {
2923     JS::AutoSuppressGCAnalysis nogc;  // bug 1582326
2924 
2925     nsINode* node;
2926     // UnwrapDOMObjectToISupports can only return non-null if its argument is
2927     // an actual DOM object, not a cross-compartment wrapper.
2928     if (NS_SUCCEEDED(UNWRAP_NON_WRAPPER_OBJECT(Node, &get(), node))) {
2929       char16_t* edgeName = nullptr;
2930       if (wantNames) {
2931         edgeName = NS_xstrdup(u"Reflected Node");
2932       }
2933       if (!range->addEdge(Edge(edgeName, node))) {
2934         return nullptr;
2935       }
2936     }
2937   }
2938   return js::UniquePtr<EdgeRange>(range.release());
2939 }
2940 
2941 }  // Namespace ubi
2942 }  // Namespace JS
2943 
ConstructUbiNode(void * storage,JSObject * ptr)2944 void ConstructUbiNode(void* storage, JSObject* ptr) {
2945   JS::ubi::ReflectorNode::construct(storage, ptr);
2946 }
2947 
Initialize(JSContext * cx)2948 void XPCJSRuntime::Initialize(JSContext* cx) {
2949   mLoaderGlobal.init(cx, nullptr);
2950 
2951   // these jsids filled in later when we have a JSContext to work with.
2952   mStrIDs[0] = JSID_VOID;
2953 
2954   nsScriptSecurityManager::GetScriptSecurityManager()->InitJSCallbacks(cx);
2955 
2956   // Unconstrain the runtime's threshold on nominal heap size, to avoid
2957   // triggering GC too often if operating continuously near an arbitrary
2958   // finite threshold (0xffffffff is infinity for uint32_t parameters).
2959   // This leaves the maximum-JS_malloc-bytes threshold still in effect
2960   // to cause period, and we hope hygienic, last-ditch GCs from within
2961   // the GC's allocator.
2962   JS_SetGCParameter(cx, JSGC_MAX_BYTES, 0xffffffff);
2963 
2964   JS_SetDestroyCompartmentCallback(cx, CompartmentDestroyedCallback);
2965   JS_SetSizeOfIncludingThisCompartmentCallback(
2966       cx, CompartmentSizeOfIncludingThisCallback);
2967   JS::SetDestroyRealmCallback(cx, DestroyRealm);
2968   JS::SetRealmNameCallback(cx, GetRealmNameCallback);
2969   mPrevGCSliceCallback = JS::SetGCSliceCallback(cx, GCSliceCallback);
2970   mPrevDoCycleCollectionCallback =
2971       JS::SetDoCycleCollectionCallback(cx, DoCycleCollectionCallback);
2972   JS_AddFinalizeCallback(cx, FinalizeCallback, nullptr);
2973   JS_AddWeakPointerZonesCallback(cx, WeakPointerZonesCallback, this);
2974   JS_AddWeakPointerCompartmentCallback(cx, WeakPointerCompartmentCallback,
2975                                        this);
2976   JS_SetWrapObjectCallbacks(cx, &WrapObjectCallbacks);
2977   if (XRE_IsE10sParentProcess()) {
2978     JS::SetFilenameValidationCallback(
2979         nsContentSecurityUtils::ValidateScriptFilename);
2980   }
2981   js::SetPreserveWrapperCallbacks(cx, PreserveWrapper, HasReleasedWrapper);
2982   JS_InitReadPrincipalsCallback(cx, nsJSPrincipals::ReadPrincipals);
2983   JS_SetAccumulateTelemetryCallback(cx, AccumulateTelemetryCallback);
2984   JS_SetSetUseCounterCallback(cx, SetUseCounterCallback);
2985 
2986   js::SetWindowProxyClass(cx, &OuterWindowProxyClass);
2987 
2988   {
2989     JS::AbortSignalIsAborted isAborted = [](JSObject* obj) {
2990       dom::AbortSignal* domObj = dom::UnwrapDOMObject<dom::AbortSignal>(obj);
2991       MOZ_ASSERT(domObj);
2992       return domObj->Aborted();
2993     };
2994 
2995     JS::InitPipeToHandling(dom::AbortSignal_Binding::GetJSClass(), isAborted,
2996                            cx);
2997   }
2998 
2999   JS::SetXrayJitInfo(&gXrayJitInfo);
3000   JS::SetProcessLargeAllocationFailureCallback(
3001       OnLargeAllocationFailureCallback);
3002   JS::SetProcessBuildIdOp(GetBuildId);
3003 
3004   // The JS engine needs to keep the source code around in order to implement
3005   // Function.prototype.toSource(). It'd be nice to not have to do this for
3006   // chrome code and simply stub out requests for source on it. Life is not so
3007   // easy, unfortunately. Nobody relies on chrome toSource() working in core
3008   // browser code, but chrome tests use it. The worst offenders are addons,
3009   // which like to monkeypatch chrome functions by calling toSource() on them
3010   // and using regular expressions to modify them. We avoid keeping most browser
3011   // JS source code in memory by setting LAZY_SOURCE on JS::CompileOptions when
3012   // compiling some chrome code. This causes the JS engine not save the source
3013   // code in memory. When the JS engine is asked to provide the source for a
3014   // function compiled with LAZY_SOURCE, it calls SourceHook to load it.
3015   ///
3016   // Note we do have to retain the source code in memory for scripts compiled in
3017   // isRunOnce mode and compiled function bodies (from
3018   // JS::CompileFunction). In practice, this means content scripts and event
3019   // handlers.
3020   mozilla::UniquePtr<XPCJSSourceHook> hook(new XPCJSSourceHook);
3021   js::SetSourceHook(cx, std::move(hook));
3022 
3023   // Register memory reporters and distinguished amount functions.
3024   RegisterStrongMemoryReporter(new JSMainRuntimeRealmsReporter());
3025   RegisterStrongMemoryReporter(new JSMainRuntimeTemporaryPeakReporter());
3026   RegisterJSMainRuntimeGCHeapDistinguishedAmount(
3027       JSMainRuntimeGCHeapDistinguishedAmount);
3028   RegisterJSMainRuntimeTemporaryPeakDistinguishedAmount(
3029       JSMainRuntimeTemporaryPeakDistinguishedAmount);
3030   RegisterJSMainRuntimeCompartmentsSystemDistinguishedAmount(
3031       JSMainRuntimeCompartmentsSystemDistinguishedAmount);
3032   RegisterJSMainRuntimeCompartmentsUserDistinguishedAmount(
3033       JSMainRuntimeCompartmentsUserDistinguishedAmount);
3034   RegisterJSMainRuntimeRealmsSystemDistinguishedAmount(
3035       JSMainRuntimeRealmsSystemDistinguishedAmount);
3036   RegisterJSMainRuntimeRealmsUserDistinguishedAmount(
3037       JSMainRuntimeRealmsUserDistinguishedAmount);
3038   mozilla::RegisterJSSizeOfTab(JSSizeOfTab);
3039 
3040   // Set the callback for reporting memory to ubi::Node.
3041   JS::ubi::SetConstructUbiNodeForDOMObjectCallback(cx, &ConstructUbiNode);
3042 
3043   xpc_LocalizeRuntime(JS_GetRuntime(cx));
3044 }
3045 
InitializeStrings(JSContext * cx)3046 bool XPCJSRuntime::InitializeStrings(JSContext* cx) {
3047   // if it is our first context then we need to generate our string ids
3048   if (JSID_IS_VOID(mStrIDs[0])) {
3049     RootedString str(cx);
3050     for (unsigned i = 0; i < XPCJSContext::IDX_TOTAL_COUNT; i++) {
3051       str = JS_AtomizeAndPinString(cx, mStrings[i]);
3052       if (!str) {
3053         mStrIDs[0] = JSID_VOID;
3054         return false;
3055       }
3056       mStrIDs[i] = PropertyKey::fromPinnedString(str);
3057       mStrJSVals[i].setString(str);
3058     }
3059 
3060     if (!mozilla::dom::DefineStaticJSVals(cx)) {
3061       return false;
3062     }
3063   }
3064 
3065   return true;
3066 }
3067 
DescribeCustomObjects(JSObject * obj,const JSClass * clasp,char (& name)[72]) const3068 bool XPCJSRuntime::DescribeCustomObjects(JSObject* obj, const JSClass* clasp,
3069                                          char (&name)[72]) const {
3070   if (clasp != &XPC_WN_Proto_JSClass) {
3071     return false;
3072   }
3073 
3074   XPCWrappedNativeProto* p =
3075       static_cast<XPCWrappedNativeProto*>(xpc_GetJSPrivate(obj));
3076   nsCOMPtr<nsIXPCScriptable> scr = p->GetScriptable();
3077   if (!scr) {
3078     return false;
3079   }
3080 
3081   SprintfLiteral(name, "JS Object (%s - %s)", clasp->name,
3082                  scr->GetJSClass()->name);
3083   return true;
3084 }
3085 
NoteCustomGCThingXPCOMChildren(const JSClass * clasp,JSObject * obj,nsCycleCollectionTraversalCallback & cb) const3086 bool XPCJSRuntime::NoteCustomGCThingXPCOMChildren(
3087     const JSClass* clasp, JSObject* obj,
3088     nsCycleCollectionTraversalCallback& cb) const {
3089   if (clasp != &XPC_WN_Tearoff_JSClass) {
3090     return false;
3091   }
3092 
3093   // A tearoff holds a strong reference to its native object
3094   // (see XPCWrappedNative::FlatJSObjectFinalized). Its XPCWrappedNative
3095   // will be held alive through tearoff's XPC_WN_TEAROFF_FLAT_OBJECT_SLOT,
3096   // which points to the XPCWrappedNative's mFlatJSObject.
3097   XPCWrappedNativeTearOff* to =
3098       static_cast<XPCWrappedNativeTearOff*>(xpc_GetJSPrivate(obj));
3099   NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "xpc_GetJSPrivate(obj)->mNative");
3100   cb.NoteXPCOMChild(to->GetNative());
3101   return true;
3102 }
3103 
3104 /***************************************************************************/
3105 
DebugDump(int16_t depth)3106 void XPCJSRuntime::DebugDump(int16_t depth) {
3107 #ifdef DEBUG
3108   depth--;
3109   XPC_LOG_ALWAYS(("XPCJSRuntime @ %p", this));
3110   XPC_LOG_INDENT();
3111 
3112   // iterate wrappers...
3113   XPC_LOG_ALWAYS(("mWrappedJSMap @ %p with %d wrappers(s)", mWrappedJSMap.get(),
3114                   mWrappedJSMap->Count()));
3115   if (depth && mWrappedJSMap->Count()) {
3116     XPC_LOG_INDENT();
3117     mWrappedJSMap->Dump(depth);
3118     XPC_LOG_OUTDENT();
3119   }
3120 
3121   XPC_LOG_ALWAYS(("mIID2NativeInterfaceMap @ %p with %d interface(s)",
3122                   mIID2NativeInterfaceMap.get(),
3123                   mIID2NativeInterfaceMap->Count()));
3124 
3125   XPC_LOG_ALWAYS(("mClassInfo2NativeSetMap @ %p with %d sets(s)",
3126                   mClassInfo2NativeSetMap.get(),
3127                   mClassInfo2NativeSetMap->Count()));
3128 
3129   XPC_LOG_ALWAYS(("mNativeSetMap @ %p with %d sets(s)", mNativeSetMap.get(),
3130                   mNativeSetMap->Count()));
3131 
3132   // iterate sets...
3133   if (depth && mNativeSetMap->Count()) {
3134     XPC_LOG_INDENT();
3135     for (auto i = mNativeSetMap->Iter(); !i.Done(); i.Next()) {
3136       auto* entry = static_cast<NativeSetMap::Entry*>(i.Get());
3137       entry->key_value->DebugDump(depth);
3138     }
3139     XPC_LOG_OUTDENT();
3140   }
3141 
3142   XPC_LOG_OUTDENT();
3143 #endif
3144 }
3145 
3146 /***************************************************************************/
3147 
AddToRootSet(XPCRootSetElem ** listHead)3148 void XPCRootSetElem::AddToRootSet(XPCRootSetElem** listHead) {
3149   MOZ_ASSERT(!mSelfp, "Must be not linked");
3150 
3151   mSelfp = listHead;
3152   mNext = *listHead;
3153   if (mNext) {
3154     MOZ_ASSERT(mNext->mSelfp == listHead, "Must be list start");
3155     mNext->mSelfp = &mNext;
3156   }
3157   *listHead = this;
3158 }
3159 
RemoveFromRootSet()3160 void XPCRootSetElem::RemoveFromRootSet() {
3161   JS::NotifyGCRootsRemoved(XPCJSContext::Get()->Context());
3162 
3163   MOZ_ASSERT(mSelfp, "Must be linked");
3164 
3165   MOZ_ASSERT(*mSelfp == this, "Link invariant");
3166   *mSelfp = mNext;
3167   if (mNext) {
3168     mNext->mSelfp = mSelfp;
3169   }
3170 #ifdef DEBUG
3171   mSelfp = nullptr;
3172   mNext = nullptr;
3173 #endif
3174 }
3175 
AddGCCallback(xpcGCCallback cb)3176 void XPCJSRuntime::AddGCCallback(xpcGCCallback cb) {
3177   MOZ_ASSERT(cb, "null callback");
3178   extraGCCallbacks.AppendElement(cb);
3179 }
3180 
RemoveGCCallback(xpcGCCallback cb)3181 void XPCJSRuntime::RemoveGCCallback(xpcGCCallback cb) {
3182   MOZ_ASSERT(cb, "null callback");
3183   bool found = extraGCCallbacks.RemoveElement(cb);
3184   if (!found) {
3185     NS_ERROR("Removing a callback which was never added.");
3186   }
3187 }
3188 
GetUAWidgetScope(JSContext * cx,nsIPrincipal * principal)3189 JSObject* XPCJSRuntime::GetUAWidgetScope(JSContext* cx,
3190                                          nsIPrincipal* principal) {
3191   MOZ_ASSERT(!principal->IsSystemPrincipal(), "Running UA Widget in chrome");
3192 
3193   RootedObject scope(cx);
3194   do {
3195     RefPtr<BasePrincipal> key = BasePrincipal::Cast(principal);
3196     if (Principal2JSObjectMap::Ptr p = mUAWidgetScopeMap.lookup(key)) {
3197       scope = p->value();
3198       break;  // Need ~RefPtr to run, and potentially GC, before returning.
3199     }
3200 
3201     SandboxOptions options;
3202     options.sandboxName.AssignLiteral("UA Widget Scope");
3203     options.wantXrays = false;
3204     options.wantComponents = false;
3205     options.isUAWidgetScope = true;
3206 
3207     // Use an ExpandedPrincipal to create asymmetric security.
3208     MOZ_ASSERT(!nsContentUtils::IsExpandedPrincipal(principal));
3209     nsTArray<nsCOMPtr<nsIPrincipal>> principalAsArray{principal};
3210     RefPtr<ExpandedPrincipal> ep = ExpandedPrincipal::Create(
3211         principalAsArray, principal->OriginAttributesRef());
3212 
3213     // Create the sandbox.
3214     RootedValue v(cx);
3215     nsresult rv = CreateSandboxObject(
3216         cx, &v, static_cast<nsIExpandedPrincipal*>(ep), options);
3217     NS_ENSURE_SUCCESS(rv, nullptr);
3218     scope = &v.toObject();
3219 
3220     JSObject* unwrapped = js::UncheckedUnwrap(scope);
3221     MOZ_ASSERT(xpc::IsInUAWidgetScope(unwrapped));
3222 
3223     MOZ_ALWAYS_TRUE(mUAWidgetScopeMap.putNew(key, unwrapped));
3224   } while (false);
3225 
3226   return scope;
3227 }
3228 
UnprivilegedJunkScope(const mozilla::fallible_t &)3229 JSObject* XPCJSRuntime::UnprivilegedJunkScope(const mozilla::fallible_t&) {
3230   if (!mUnprivilegedJunkScope) {
3231     dom::AutoJSAPI jsapi;
3232     jsapi.Init();
3233     JSContext* cx = jsapi.cx();
3234 
3235     SandboxOptions options;
3236     options.sandboxName.AssignLiteral("XPConnect Junk Compartment");
3237     options.invisibleToDebugger = true;
3238 
3239     RootedValue sandbox(cx);
3240     nsresult rv = CreateSandboxObject(cx, &sandbox, nullptr, options);
3241     NS_ENSURE_SUCCESS(rv, nullptr);
3242 
3243     mUnprivilegedJunkScope =
3244         SandboxPrivate::GetPrivate(sandbox.toObjectOrNull());
3245   }
3246   MOZ_ASSERT(mUnprivilegedJunkScope->GetWrapper(),
3247              "Wrapper should have same lifetime as weak reference");
3248   return mUnprivilegedJunkScope->GetWrapper();
3249 }
3250 
UnprivilegedJunkScope()3251 JSObject* XPCJSRuntime::UnprivilegedJunkScope() {
3252   JSObject* scope = UnprivilegedJunkScope(fallible);
3253   MOZ_RELEASE_ASSERT(scope);
3254   return scope;
3255 }
3256 
IsUnprivilegedJunkScope(JSObject * obj)3257 bool XPCJSRuntime::IsUnprivilegedJunkScope(JSObject* obj) {
3258   return mUnprivilegedJunkScope && obj == mUnprivilegedJunkScope->GetWrapper();
3259 }
3260 
DeleteSingletonScopes()3261 void XPCJSRuntime::DeleteSingletonScopes() {
3262   // We're pretty late in shutdown, so we call ReleaseWrapper on the scopes.
3263   // This way the GC can collect them immediately, and we don't rely on the CC
3264   // to clean up.
3265   if (RefPtr<SandboxPrivate> sandbox = mUnprivilegedJunkScope.get()) {
3266     sandbox->ReleaseWrapper(sandbox);
3267     mUnprivilegedJunkScope = nullptr;
3268   }
3269   mLoaderGlobal = nullptr;
3270 }
3271 
LoaderGlobal()3272 JSObject* XPCJSRuntime::LoaderGlobal() {
3273   if (!mLoaderGlobal) {
3274     RefPtr<mozJSComponentLoader> loader = mozJSComponentLoader::Get();
3275 
3276     dom::AutoJSAPI jsapi;
3277     jsapi.Init();
3278 
3279     mLoaderGlobal = loader->GetSharedGlobal(jsapi.cx());
3280     MOZ_RELEASE_ASSERT(!JS_IsExceptionPending(jsapi.cx()));
3281   }
3282   return mLoaderGlobal;
3283 }
3284 
GetAndClampCPUCount()3285 uint32_t GetAndClampCPUCount() {
3286   // See HelperThreads.cpp for why we want between 2-8 threads
3287   int32_t proc = GetNumberOfProcessors();
3288   if (proc < 2) {
3289     return 2;
3290   }
3291   return std::min(proc, 8);
3292 }
3293