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