Source code
Revision control
Copy as Markdown
Other Tools
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
/* Per JSRuntime object */
#include "mozilla/ArrayUtils.h"
#include "mozilla/AutoRestore.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/UniquePtr.h"
#include "xpcprivate.h"
#include "xpcpublic.h"
#include "XPCMaps.h"
#include "XPCWrapper.h"
#include "XPCJSMemoryReporter.h"
#include "XrayWrapper.h"
#include "WrapperFactory.h"
#include "mozJSModuleLoader.h"
#include "nsNetUtil.h"
#include "nsContentSecurityUtils.h"
#include "nsExceptionHandler.h"
#include "nsIMemoryInfoDumper.h"
#include "nsIMemoryReporter.h"
#include "nsIObserverService.h"
#include "mozilla/dom/Document.h"
#include "nsIRunnable.h"
#include "nsIPlatformInfo.h"
#include "nsPIDOMWindow.h"
#include "nsPrintfCString.h"
#include "nsScriptSecurityManager.h"
#include "nsThreadPool.h"
#include "nsWindowSizes.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/Preferences.h"
#include "mozilla/Telemetry.h"
#include "mozilla/Services.h"
#include "mozilla/dom/ScriptLoader.h"
#include "mozilla/dom/ScriptSettings.h"
#include "nsContentUtils.h"
#include "nsCCUncollectableMarker.h"
#include "nsCycleCollectionNoteRootCallback.h"
#include "nsCycleCollector.h"
#include "jsapi.h"
#include "js/BuildId.h" // JS::BuildIdCharVector, JS::SetProcessBuildIdOp
#include "js/experimental/SourceHook.h" // js::{,Set}SourceHook
#include "js/GCAPI.h"
#include "js/MemoryFunctions.h"
#include "js/MemoryMetrics.h"
#include "js/Object.h" // JS::GetClass
#include "js/RealmIterators.h"
#include "js/SliceBudget.h"
#include "js/UbiNode.h"
#include "js/UbiNodeUtils.h"
#include "js/friend/UsageStatistics.h" // JSMetric, JS_SetAccumulateTelemetryCallback
#include "js/friend/WindowProxy.h" // js::SetWindowProxyClass
#include "js/friend/XrayJitInfo.h" // JS::SetXrayJitInfo
#include "mozilla/dom/AbortSignalBinding.h"
#include "mozilla/dom/GeneratedAtomList.h"
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/FetchUtil.h"
#include "mozilla/dom/WindowBinding.h"
#include "mozilla/Atomics.h"
#include "mozilla/Attributes.h"
#include "mozilla/ProcessHangMonitor.h"
#include "mozilla/ProfilerLabels.h"
#include "mozilla/Sprintf.h"
#include "mozilla/UniquePtrExtensions.h"
#include "mozilla/Unused.h"
#include "AccessCheck.h"
#include "nsGlobalWindow.h"
#include "nsAboutProtocolUtils.h"
#include "NodeUbiReporting.h"
#include "ExpandedPrincipal.h"
#include "nsIInputStream.h"
#include "nsJSPrincipals.h"
#include "nsJSEnvironment.h"
#include "XPCInlines.h"
#ifdef XP_WIN
# include <windows.h>
#endif
using namespace mozilla;
using namespace mozilla::dom;
using namespace xpc;
using namespace JS;
using namespace js;
using mozilla::dom::PerThreadAtomCache;
/***************************************************************************/
const char* const XPCJSRuntime::mStrings[] = {
"constructor", // IDX_CONSTRUCTOR
"toString", // IDX_TO_STRING
"toSource", // IDX_TO_SOURCE
"value", // IDX_VALUE
"QueryInterface", // IDX_QUERY_INTERFACE
"Components", // IDX_COMPONENTS
"Cc", // IDX_CC
"Ci", // IDX_CI
"Cr", // IDX_CR
"Cu", // IDX_CU
"Services", // IDX_SERVICES
"wrappedJSObject", // IDX_WRAPPED_JSOBJECT
"prototype", // IDX_PROTOTYPE
"eval", // IDX_EVAL
"controllers", // IDX_CONTROLLERS
"Controllers", // IDX_CONTROLLERS_CLASS
"length", // IDX_LENGTH
"name", // IDX_NAME
"undefined", // IDX_UNDEFINED
"", // IDX_EMPTYSTRING
"fileName", // IDX_FILENAME
"lineNumber", // IDX_LINENUMBER
"columnNumber", // IDX_COLUMNNUMBER
"stack", // IDX_STACK
"message", // IDX_MESSAGE
"cause", // IDX_CAUSE
"errors", // IDX_ERRORS
"lastIndex", // IDX_LASTINDEX
"then", // IDX_THEN
"isInstance", // IDX_ISINSTANCE
"Infinity", // IDX_INFINITY
"NaN", // IDX_NAN
"classId", // IDX_CLASS_ID
"interfaceId", // IDX_INTERFACE_ID
"initializer", // IDX_INITIALIZER
"print", // IDX_PRINT
"fetch", // IDX_FETCH
"crypto", // IDX_CRYPTO
"indexedDB", // IDX_INDEXEDDB
"structuredClone", // IDX_STRUCTUREDCLONE
};
/***************************************************************************/
// *Some* NativeSets are referenced from mClassInfo2NativeSetMap.
// *All* NativeSets are referenced from mNativeSetMap.
// So, in mClassInfo2NativeSetMap we just clear references to the unmarked.
// In mNativeSetMap we clear the references to the unmarked *and* delete them.
class AsyncFreeSnowWhite : public Runnable {
public:
NS_IMETHOD Run() override {
AUTO_PROFILER_LABEL_RELEVANT_FOR_JS("Incremental CC", GCCC);
AUTO_PROFILER_LABEL("AsyncFreeSnowWhite::Run", GCCC_FreeSnowWhite);
TimeStamp start = TimeStamp::Now();
// 2 ms budget, given that kICCSliceBudget is only 3 ms
js::SliceBudget budget = js::SliceBudget(js::TimeBudget(2));
bool hadSnowWhiteObjects =
nsCycleCollector_doDeferredDeletionWithBudget(budget);
Telemetry::Accumulate(
Telemetry::CYCLE_COLLECTOR_ASYNC_SNOW_WHITE_FREEING,
uint32_t((TimeStamp::Now() - start).ToMilliseconds()));
if (hadSnowWhiteObjects && !mContinuation) {
mContinuation = true;
if (NS_FAILED(Dispatch())) {
mActive = false;
}
} else {
mActive = false;
}
return NS_OK;
}
nsresult Dispatch() {
nsCOMPtr<nsIRunnable> self(this);
return NS_DispatchToCurrentThreadQueue(self.forget(), 500,
EventQueuePriority::Idle);
}
void Start(bool aContinuation = false, bool aPurge = false) {
if (mContinuation) {
mContinuation = aContinuation;
}
mPurge = aPurge;
if (!mActive && NS_SUCCEEDED(Dispatch())) {
mActive = true;
}
}
AsyncFreeSnowWhite()
: Runnable("AsyncFreeSnowWhite"),
mContinuation(false),
mActive(false),
mPurge(false) {}
public:
bool mContinuation;
bool mActive;
bool mPurge;
};
namespace xpc {
CompartmentPrivate::CompartmentPrivate(
JS::Compartment* c, mozilla::UniquePtr<XPCWrappedNativeScope> scope,
mozilla::BasePrincipal* origin, const SiteIdentifier& site)
: originInfo(origin, site),
wantXrays(false),
allowWaivers(true),
isWebExtensionContentScript(false),
isUAWidgetCompartment(false),
hasExclusiveExpandos(false),
wasShutdown(false),
mWrappedJSMap(mozilla::MakeUnique<JSObject2WrappedJSMap>()),
mScope(std::move(scope)) {
MOZ_COUNT_CTOR(xpc::CompartmentPrivate);
}
CompartmentPrivate::~CompartmentPrivate() {
MOZ_COUNT_DTOR(xpc::CompartmentPrivate);
}
void CompartmentPrivate::SystemIsBeingShutDown() {
// We may call this multiple times when the compartment contains more than one
// realm.
if (!wasShutdown) {
mWrappedJSMap->ShutdownMarker();
wasShutdown = true;
}
}
RealmPrivate::RealmPrivate(JS::Realm* realm) : scriptability(realm) {
mozilla::PodArrayZero(wrapperDenialWarnings);
}
/* static */
void RealmPrivate::Init(HandleObject aGlobal, const SiteIdentifier& aSite) {
MOZ_ASSERT(aGlobal);
DebugOnly<const JSClass*> clasp = JS::GetClass(aGlobal);
MOZ_ASSERT(clasp->slot0IsISupports() || dom::IsDOMClass(clasp));
Realm* realm = GetObjectRealmOrNull(aGlobal);
// Create the realm private.
RealmPrivate* realmPriv = new RealmPrivate(realm);
MOZ_ASSERT(!GetRealmPrivate(realm));
SetRealmPrivate(realm, realmPriv);
nsIPrincipal* principal = GetRealmPrincipal(realm);
Compartment* c = JS::GetCompartment(aGlobal);
// Create the compartment private if needed.
if (CompartmentPrivate* priv = CompartmentPrivate::Get(c)) {
MOZ_ASSERT(priv->originInfo.IsSameOrigin(principal));
} else {
auto scope = mozilla::MakeUnique<XPCWrappedNativeScope>(c, aGlobal);
priv = new CompartmentPrivate(c, std::move(scope),
BasePrincipal::Cast(principal), aSite);
JS_SetCompartmentPrivate(c, priv);
}
}
// As XPCJSRuntime can live longer than when we shutdown the observer service,
// we have our own getter to account for this.
static nsCOMPtr<nsIObserverService> GetObserverService() {
if (AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMShutdownFinal)) {
return nullptr;
}
return mozilla::services::GetObserverService();
}
static bool TryParseLocationURICandidate(
const nsACString& uristr, RealmPrivate::LocationHint aLocationHint,
nsIURI** aURI) {
if (aLocationHint == RealmPrivate::LocationHintAddon) {
// Blacklist some known locations which are clearly not add-on related.
if (StringBeginsWith(uristr, kGRE) || StringBeginsWith(uristr, kToolkit) ||
StringBeginsWith(uristr, kBrowser)) {
return false;
}
// -- GROSS HACK ALERT --
// The Yandex Elements 8.10.2 extension implements its own "xb://" URL
// scheme. If we call NS_NewURI() on an "xb://..." URL, we'll end up
// calling into the extension's own JS-implemented nsIProtocolHandler
// object, which we can't allow while we're iterating over the JS heap.
// So just skip any such URL.
// -- GROSS HACK ALERT --
if (StringBeginsWith(uristr, "xb"_ns)) {
return false;
}
}
nsCOMPtr<nsIURI> uri;
if (NS_FAILED(NS_NewURI(getter_AddRefs(uri), uristr))) {
return false;
}
nsAutoCString scheme;
if (NS_FAILED(uri->GetScheme(scheme))) {
return false;
}
// Cannot really map data: and blob:.
// Also, data: URIs are pretty memory hungry, which is kinda bad
// for memory reporter use.
if (scheme.EqualsLiteral("data") || scheme.EqualsLiteral("blob")) {
return false;
}
uri.forget(aURI);
return true;
}
bool RealmPrivate::TryParseLocationURI(RealmPrivate::LocationHint aLocationHint,
nsIURI** aURI) {
if (!aURI) {
return false;
}
// Need to parse the URI.
if (location.IsEmpty()) {
return false;
}
// Handle Sandbox location strings.
// A sandbox string looks like this, for anonymous sandboxes, and builds
// where Sandbox location tagging is enabled:
//
// <sandboxName> (from: <js-stack-frame-filename>:<lineno>)
//
// where <sandboxName> is user-provided via Cu.Sandbox()
// and <js-stack-frame-filename> and <lineno> is the stack frame location
// from where Cu.Sandbox was called.
//
// Otherwise, it is simply the caller-provided name, which is usually a URI.
//
// <js-stack-frame-filename> furthermore is "free form", often using a
// "uri -> uri -> ..." chain. The following code will and must handle this
// common case.
//
// It should be noted that other parts of the code may already rely on the
// "format" of these strings.
static const nsDependentCString from("(from: ");
static const nsDependentCString arrow(" -> ");
static const size_t fromLength = from.Length();
static const size_t arrowLength = arrow.Length();
// See: XPCComponents.cpp#AssembleSandboxMemoryReporterName
int32_t idx = location.Find(from);
if (idx < 0) {
return TryParseLocationURICandidate(location, aLocationHint, aURI);
}
// When parsing we're looking for the right-most URI. This URI may be in
// <sandboxName>, so we try this first.
if (TryParseLocationURICandidate(Substring(location, 0, idx), aLocationHint,
aURI)) {
return true;
}
// Not in <sandboxName> so we need to inspect <js-stack-frame-filename> and
// the chain that is potentially contained within and grab the rightmost
// item that is actually a URI.
// First, hack off the :<lineno>) part as well
int32_t ridx = location.RFind(":"_ns);
nsAutoCString chain(
Substring(location, idx + fromLength, ridx - idx - fromLength));
// Loop over the "->" chain. This loop also works for non-chains, or more
// correctly chains with only one item.
for (;;) {
idx = chain.RFind(arrow);
if (idx < 0) {
// This is the last chain item. Try to parse what is left.
return TryParseLocationURICandidate(chain, aLocationHint, aURI);
}
// Try to parse current chain item
if (TryParseLocationURICandidate(Substring(chain, idx + arrowLength),
aLocationHint, aURI)) {
return true;
}
// Current chain item couldn't be parsed.
// Strip current item and continue.
chain = Substring(chain, 0, idx);
}
MOZ_CRASH("Chain parser loop does not terminate");
}
static bool PrincipalImmuneToScriptPolicy(nsIPrincipal* aPrincipal) {
// System principal gets a free pass.
if (aPrincipal->IsSystemPrincipal()) {
return true;
}
auto* principal = BasePrincipal::Cast(aPrincipal);
// ExpandedPrincipal gets a free pass.
if (principal->Is<ExpandedPrincipal>()) {
return true;
}
// WebExtension principals get a free pass.
if (principal->AddonPolicy()) {
return true;
}
// pdf.js is a special-case too.
if (nsContentUtils::IsPDFJS(principal)) {
return true;
}
// Check whether our URI is an "about:" URI that allows scripts. If it is,
// we need to allow JS to run.
if (aPrincipal->SchemeIs("about")) {
uint32_t flags;
nsresult rv = aPrincipal->GetAboutModuleFlags(&flags);
if (NS_SUCCEEDED(rv) && (flags & nsIAboutModule::ALLOW_SCRIPT)) {
return true;
}
}
return false;
}
void RealmPrivate::RegisterStackFrame(JSStackFrameBase* aFrame) {
mJSStackFrames.PutEntry(aFrame);
}
void RealmPrivate::UnregisterStackFrame(JSStackFrameBase* aFrame) {
mJSStackFrames.RemoveEntry(aFrame);
}
void RealmPrivate::NukeJSStackFrames() {
for (const auto& key : mJSStackFrames.Keys()) {
key->Clear();
}
mJSStackFrames.Clear();
}
void RegisterJSStackFrame(JS::Realm* aRealm, JSStackFrameBase* aStackFrame) {
RealmPrivate* realmPrivate = RealmPrivate::Get(aRealm);
if (!realmPrivate) {
return;
}
realmPrivate->RegisterStackFrame(aStackFrame);
}
void UnregisterJSStackFrame(JS::Realm* aRealm, JSStackFrameBase* aStackFrame) {
RealmPrivate* realmPrivate = RealmPrivate::Get(aRealm);
if (!realmPrivate) {
return;
}
realmPrivate->UnregisterStackFrame(aStackFrame);
}
void NukeJSStackFrames(JS::Realm* aRealm) {
RealmPrivate* realmPrivate = RealmPrivate::Get(aRealm);
if (!realmPrivate) {
return;
}
realmPrivate->NukeJSStackFrames();
}
Scriptability::Scriptability(JS::Realm* realm)
: mScriptBlocks(0),
mWindowAllowsScript(true),
mScriptBlockedByPolicy(false) {
nsIPrincipal* prin = nsJSPrincipals::get(JS::GetRealmPrincipals(realm));
mImmuneToScriptPolicy = PrincipalImmuneToScriptPolicy(prin);
if (mImmuneToScriptPolicy) {
return;
}
// If we're not immune, we should have a real principal with a URI.
// Check the principal against the new-style domain policy.
bool policyAllows;
nsresult rv = prin->GetIsScriptAllowedByPolicy(&policyAllows);
if (NS_SUCCEEDED(rv)) {
mScriptBlockedByPolicy = !policyAllows;
return;
}
// Something went wrong - be safe and block script.
mScriptBlockedByPolicy = true;
}
bool Scriptability::Allowed() {
return mWindowAllowsScript && !mScriptBlockedByPolicy && mScriptBlocks == 0;
}
bool Scriptability::IsImmuneToScriptPolicy() { return mImmuneToScriptPolicy; }
void Scriptability::Block() { ++mScriptBlocks; }
void Scriptability::Unblock() {
MOZ_ASSERT(mScriptBlocks > 0);
--mScriptBlocks;
}
void Scriptability::SetWindowAllowsScript(bool aAllowed) {
mWindowAllowsScript = aAllowed || mImmuneToScriptPolicy;
}
/* static */
bool Scriptability::AllowedIfExists(JSObject* aScope) {
RealmPrivate* realmPrivate = RealmPrivate::Get(aScope);
return realmPrivate ? realmPrivate->scriptability.Allowed() : true;
}
/* static */
Scriptability& Scriptability::Get(JSObject* aScope) {
return RealmPrivate::Get(aScope)->scriptability;
}
bool IsUAWidgetCompartment(JS::Compartment* compartment) {
// We always eagerly create compartment privates for UA Widget compartments.
CompartmentPrivate* priv = CompartmentPrivate::Get(compartment);
return priv && priv->isUAWidgetCompartment;
}
bool IsUAWidgetScope(JS::Realm* realm) {
return IsUAWidgetCompartment(JS::GetCompartmentForRealm(realm));
}
bool IsInUAWidgetScope(JSObject* obj) {
return IsUAWidgetCompartment(JS::GetCompartment(obj));
}
bool CompartmentOriginInfo::MightBeWebContent() const {
// Compartments with principals that are either the system principal or an
// expanded principal are definitely not web content.
return !nsContentUtils::IsSystemOrExpandedPrincipal(mOrigin);
}
bool MightBeWebContentCompartment(JS::Compartment* compartment) {
if (CompartmentPrivate* priv = CompartmentPrivate::Get(compartment)) {
return priv->originInfo.MightBeWebContent();
}
// No CompartmentPrivate; try IsSystemCompartment.
return !js::IsSystemCompartment(compartment);
}
bool CompartmentOriginInfo::IsSameOrigin(nsIPrincipal* aOther) const {
return mOrigin->FastEquals(aOther);
}
/* static */
bool CompartmentOriginInfo::Subsumes(JS::Compartment* aCompA,
JS::Compartment* aCompB) {
CompartmentPrivate* apriv = CompartmentPrivate::Get(aCompA);
CompartmentPrivate* bpriv = CompartmentPrivate::Get(aCompB);
MOZ_ASSERT(apriv);
MOZ_ASSERT(bpriv);
return apriv->originInfo.mOrigin->FastSubsumes(bpriv->originInfo.mOrigin);
}
/* static */
bool CompartmentOriginInfo::SubsumesIgnoringFPD(JS::Compartment* aCompA,
JS::Compartment* aCompB) {
CompartmentPrivate* apriv = CompartmentPrivate::Get(aCompA);
CompartmentPrivate* bpriv = CompartmentPrivate::Get(aCompB);
MOZ_ASSERT(apriv);
MOZ_ASSERT(bpriv);
return apriv->originInfo.mOrigin->FastSubsumesIgnoringFPD(
bpriv->originInfo.mOrigin);
}
void SetCompartmentChangedDocumentDomain(JS::Compartment* compartment) {
// Note: we call this for all compartments that contain realms with a
// particular principal. Not all of these compartments have a
// CompartmentPrivate (for instance the temporary compartment/realm
// created by the JS engine for off-thread parsing).
if (CompartmentPrivate* priv = CompartmentPrivate::Get(compartment)) {
priv->originInfo.SetChangedDocumentDomain();
}
}
JSObject* UnprivilegedJunkScope() {
return XPCJSRuntime::Get()->UnprivilegedJunkScope();
}
JSObject* UnprivilegedJunkScope(const fallible_t&) {
return XPCJSRuntime::Get()->UnprivilegedJunkScope(fallible);
}
bool IsUnprivilegedJunkScope(JSObject* obj) {
return XPCJSRuntime::Get()->IsUnprivilegedJunkScope(obj);
}
JSObject* NACScope(JSObject* global) {
// If we're a chrome global, just use ourselves.
if (AccessCheck::isChrome(global)) {
return global;
}
JSObject* scope = UnprivilegedJunkScope();
JS::ExposeObjectToActiveJS(scope);
return scope;
}
JSObject* PrivilegedJunkScope() { return XPCJSRuntime::Get()->LoaderGlobal(); }
JSObject* CompilationScope() { return XPCJSRuntime::Get()->LoaderGlobal(); }
nsGlobalWindowInner* WindowOrNull(JSObject* aObj) {
MOZ_ASSERT(aObj);
MOZ_ASSERT(!js::IsWrapper(aObj));
nsGlobalWindowInner* win = nullptr;
UNWRAP_NON_WRAPPER_OBJECT(Window, aObj, win);
return win;
}
nsGlobalWindowInner* WindowGlobalOrNull(JSObject* aObj) {
MOZ_ASSERT(aObj);
JSObject* glob = JS::GetNonCCWObjectGlobal(aObj);
return WindowOrNull(glob);
}
nsGlobalWindowInner* SandboxWindowOrNull(JSObject* aObj, JSContext* aCx) {
MOZ_ASSERT(aObj);
if (!IsSandbox(aObj)) {
return nullptr;
}
// Sandbox can't be a Proxy so it must have a static prototype.
JSObject* proto = GetStaticPrototype(aObj);
if (!proto || !IsSandboxPrototypeProxy(proto)) {
return nullptr;
}
proto = js::CheckedUnwrapDynamic(proto, aCx, /* stopAtWindowProxy = */ false);
if (!proto) {
return nullptr;
}
return WindowOrNull(proto);
}
nsGlobalWindowInner* CurrentWindowOrNull(JSContext* cx) {
JSObject* glob = JS::CurrentGlobalOrNull(cx);
return glob ? WindowOrNull(glob) : nullptr;
}
// Nukes all wrappers into or out of the given realm, and prevents new
// wrappers from being created. Additionally marks the realm as
// unscriptable after wrappers have been nuked.
//
// Note: This should *only* be called for browser or extension realms.
// Wrappers between web compartments must never be cut in web-observable
// ways.
void NukeAllWrappersForRealm(
JSContext* cx, JS::Realm* realm,
js::NukeReferencesToWindow nukeReferencesToWindow) {
// We do the following:
// * Nuke all wrappers into the realm.
// * Nuke all wrappers out of the realm's compartment, once we have nuked all
// realms in it.
js::NukeCrossCompartmentWrappers(cx, js::AllCompartments(), realm,
nukeReferencesToWindow,
js::NukeAllReferences);
// Mark the realm as unscriptable.
xpc::RealmPrivate::Get(realm)->scriptability.Block();
}
} // namespace xpc
static void CompartmentDestroyedCallback(JS::GCContext* gcx,
JS::Compartment* compartment) {
// NB - This callback may be called in JS_DestroyContext, which happens
// after the XPCJSRuntime has been torn down.
// Get the current compartment private into a UniquePtr (which will do the
// cleanup for us), and null out the private (which may already be null).
mozilla::UniquePtr<CompartmentPrivate> priv(
CompartmentPrivate::Get(compartment));
JS_SetCompartmentPrivate(compartment, nullptr);
}
static size_t CompartmentSizeOfIncludingThisCallback(
MallocSizeOf mallocSizeOf, JS::Compartment* compartment) {
CompartmentPrivate* priv = CompartmentPrivate::Get(compartment);
return priv ? priv->SizeOfIncludingThis(mallocSizeOf) : 0;
}
/*
* Return true if there exists a non-system inner window which is a current
* inner window and whose reflector is gray. We don't merge system
* compartments, so we don't use them to trigger merging CCs.
*/
bool XPCJSRuntime::UsefulToMergeZones() const {
MOZ_ASSERT(NS_IsMainThread());
// Turns out, actually making this return true often enough makes Windows
// mochitest-gl OOM a lot. Need to figure out what's going on there; see
return false;
}
void XPCJSRuntime::TraceNativeBlackRoots(JSTracer* trc) {
if (CycleCollectedJSContext* ccx = GetContext()) {
const auto* cx = static_cast<const XPCJSContext*>(ccx);
if (AutoMarkingPtr* roots = cx->mAutoRoots) {
roots->TraceJSAll(trc);
}
}
if (mIID2NativeInterfaceMap) {
mIID2NativeInterfaceMap->Trace(trc);
}
dom::TraceBlackJS(trc);
}
void XPCJSRuntime::TraceAdditionalNativeGrayRoots(JSTracer* trc) {
XPCWrappedNativeScope::TraceWrappedNativesInAllScopes(this, trc);
}
void XPCJSRuntime::TraverseAdditionalNativeRoots(
nsCycleCollectionNoteRootCallback& cb) {
XPCWrappedNativeScope::SuspectAllWrappers(cb);
auto* parti = NS_CYCLE_COLLECTION_PARTICIPANT(nsXPCWrappedJS);
for (auto* wjs : mSubjectToFinalizationWJS) {
MOZ_DIAGNOSTIC_ASSERT(wjs->IsSubjectToFinalization());
cb.NoteXPCOMRoot(ToSupports(wjs), parti);
}
}
void XPCJSRuntime::UnmarkSkippableJSHolders() {
CycleCollectedJSRuntime::UnmarkSkippableJSHolders();
}
void XPCJSRuntime::PrepareForForgetSkippable() {
nsCOMPtr<nsIObserverService> obs = xpc::GetObserverService();
if (obs) {
obs->NotifyObservers(nullptr, "cycle-collector-forget-skippable", nullptr);
}
}
void XPCJSRuntime::BeginCycleCollectionCallback(CCReason aReason) {
nsJSContext::BeginCycleCollectionCallback(aReason);
nsCOMPtr<nsIObserverService> obs = xpc::GetObserverService();
if (obs) {
obs->NotifyObservers(nullptr, "cycle-collector-begin", nullptr);
}
}
void XPCJSRuntime::EndCycleCollectionCallback(CycleCollectorResults& aResults) {
nsJSContext::EndCycleCollectionCallback(aResults);
nsCOMPtr<nsIObserverService> obs = xpc::GetObserverService();
if (obs) {
obs->NotifyObservers(nullptr, "cycle-collector-end", nullptr);
}
}
void XPCJSRuntime::DispatchDeferredDeletion(bool aContinuation, bool aPurge) {
mAsyncSnowWhiteFreer->Start(aContinuation, aPurge);
}
void xpc_UnmarkSkippableJSHolders() {
if (nsXPConnect::GetRuntimeInstance()) {
nsXPConnect::GetRuntimeInstance()->UnmarkSkippableJSHolders();
}
}
/* static */
void XPCJSRuntime::GCSliceCallback(JSContext* cx, JS::GCProgress progress,
const JS::GCDescription& desc) {
XPCJSRuntime* self = nsXPConnect::GetRuntimeInstance();
if (!self) {
return;
}
nsCOMPtr<nsIObserverService> obs = xpc::GetObserverService();
if (obs) {
switch (progress) {
case JS::GC_CYCLE_BEGIN:
obs->NotifyObservers(nullptr, "garbage-collector-begin", nullptr);
break;
case JS::GC_CYCLE_END:
obs->NotifyObservers(nullptr, "garbage-collector-end", nullptr);
break;
default:
break;
}
}
CrashReporter::SetGarbageCollecting(progress == JS::GC_CYCLE_BEGIN);
if (self->mPrevGCSliceCallback) {
(*self->mPrevGCSliceCallback)(cx, progress, desc);
}
}
/* static */
void XPCJSRuntime::DoCycleCollectionCallback(JSContext* cx) {
// The GC has detected that a CC at this point would collect a tremendous
// amount of garbage that is being revivified unnecessarily.
//
// The GC_WAITING reason is a little overloaded here, but we want to do
// a CC to allow Realms to be collected when they are referenced by a cycle.
NS_DispatchToCurrentThread(NS_NewRunnableFunction(
"XPCJSRuntime::DoCycleCollectionCallback",
[]() { nsJSContext::CycleCollectNow(CCReason::GC_WAITING, nullptr); }));
XPCJSRuntime* self = nsXPConnect::GetRuntimeInstance();
if (!self) {
return;
}
if (self->mPrevDoCycleCollectionCallback) {
(*self->mPrevDoCycleCollectionCallback)(cx);
}
}
void XPCJSRuntime::CustomGCCallback(JSGCStatus status) {
nsTArray<xpcGCCallback> callbacks(extraGCCallbacks.Clone());
for (uint32_t i = 0; i < callbacks.Length(); ++i) {
callbacks[i](status);
}
}
/* static */
void XPCJSRuntime::FinalizeCallback(JS::GCContext* gcx, JSFinalizeStatus status,
void* data) {
XPCJSRuntime* self = nsXPConnect::GetRuntimeInstance();
if (!self) {
return;
}
switch (status) {
case JSFINALIZE_GROUP_PREPARE: {
MOZ_ASSERT(!self->mDoingFinalization, "bad state");
MOZ_ASSERT(!self->mGCIsRunning, "bad state");
self->mGCIsRunning = true;
self->mDoingFinalization = true;
break;
}
case JSFINALIZE_GROUP_START: {
MOZ_ASSERT(self->mDoingFinalization, "bad state");
MOZ_ASSERT(self->mGCIsRunning, "bad state");
self->mGCIsRunning = false;
break;
}
case JSFINALIZE_GROUP_END: {
MOZ_ASSERT(self->mDoingFinalization, "bad state");
self->mDoingFinalization = false;
break;
}
case JSFINALIZE_COLLECTION_END: {
MOZ_ASSERT(!self->mGCIsRunning, "bad state");
self->mGCIsRunning = true;
if (CycleCollectedJSContext* ccx = self->GetContext()) {
const auto* cx = static_cast<const XPCJSContext*>(ccx);
if (AutoMarkingPtr* roots = cx->mAutoRoots) {
roots->MarkAfterJSFinalizeAll();
}
// Now we are going to recycle any unused WrappedNativeTearoffs.
// We do this by iterating all the live callcontexts
// and marking the tearoffs in use. And then we
// iterate over all the WrappedNative wrappers and sweep their
// tearoffs.
//
// This allows us to perhaps minimize the growth of the
// tearoffs. And also makes us not hold references to interfaces
// on our wrapped natives that we are not actually using.
//
// XXX We may decide to not do this on *every* gc cycle.
XPCCallContext* ccxp = cx->GetCallContext();
while (ccxp) {
// Deal with the strictness of callcontext that
// complains if you ask for a tearoff when
// it is in a state where the tearoff could not
// possibly be valid.
if (ccxp->CanGetTearOff()) {
XPCWrappedNativeTearOff* to = ccxp->GetTearOff();
if (to) {
to->Mark();
}
}
ccxp = ccxp->GetPrevCallContext();
}
}
XPCWrappedNativeScope::SweepAllWrappedNativeTearOffs();
// Now we need to kill the 'Dying' XPCWrappedNativeProtos.
//
// We transferred these native objects to this list when their JSObjects
// were finalized. We did not destroy them immediately at that point
// because the ordering of JS finalization is not deterministic and we did
// not yet know if any wrappers that might still be referencing the protos
// were still yet to be finalized and destroyed. We *do* know that the
// protos' JSObjects would not have been finalized if there were any
// wrappers that referenced the proto but were not themselves slated for
// finalization in this gc cycle.
//
// At this point we know that any and all wrappers that might have been
// referencing the protos in the dying list are themselves dead. So, we
// can safely delete all the protos in the list.
self->mDyingWrappedNativeProtos.clear();
MOZ_ASSERT(self->mGCIsRunning, "bad state");
self->mGCIsRunning = false;
break;
}
}
}
/* static */
void XPCJSRuntime::WeakPointerZonesCallback(JSTracer* trc, void* data) {
// Called before each sweeping slice -- after processing any final marking
// triggered by barriers -- to clear out any references to things that are
// about to be finalized and update any pointers to moved GC things.
XPCJSRuntime* self = static_cast<XPCJSRuntime*>(data);
// This callback is always called from within the GC so set the mGCIsRunning
// flag to prevent AssertInvalidWrappedJSNotInTable from trying to call back
// into the JS API. This has often already been set by FinalizeCallback by the
// time we get here, but it may not be if we are doing a shutdown GC or if we
// are called for compacting GC.
AutoRestore<bool> restoreState(self->mGCIsRunning);
self->mGCIsRunning = true;
self->mWrappedJSMap->UpdateWeakPointersAfterGC(trc);
self->mUAWidgetScopeMap.traceWeak(trc);
}
/* static */
void XPCJSRuntime::WeakPointerCompartmentCallback(JSTracer* trc,
JS::Compartment* comp,
void* data) {
// Called immediately after the ZoneGroup weak pointer callback, but only
// once for each compartment that is being swept.
CompartmentPrivate* xpcComp = CompartmentPrivate::Get(comp);
if (xpcComp) {
xpcComp->UpdateWeakPointersAfterGC(trc);
}
}
void CompartmentPrivate::UpdateWeakPointersAfterGC(JSTracer* trc) {
mRemoteProxies.traceWeak(trc);
mWrappedJSMap->UpdateWeakPointersAfterGC(trc);
mScope->UpdateWeakPointersAfterGC(trc);
}
void XPCJSRuntime::CustomOutOfMemoryCallback() {
if (!Preferences::GetBool("memory.dump_reports_on_oom")) {
return;
}
nsCOMPtr<nsIMemoryInfoDumper> dumper =
do_GetService("@mozilla.org/memory-info-dumper;1");
if (!dumper) {
return;
}
// If this fails, it fails silently.
dumper->DumpMemoryInfoToTempDir(u"due-to-JS-OOM"_ns,
/* anonymize = */ false,
/* minimizeMemoryUsage = */ false);
}
void XPCJSRuntime::OnLargeAllocationFailure() {
CycleCollectedJSRuntime::SetLargeAllocationFailure(OOMState::Reporting);
nsCOMPtr<nsIObserverService> os = xpc::GetObserverService();
if (os) {
os->NotifyObservers(nullptr, "memory-pressure", u"heap-minimize");
}
CycleCollectedJSRuntime::SetLargeAllocationFailure(OOMState::Reported);
}
class LargeAllocationFailureRunnable final : public Runnable {
Mutex mMutex MOZ_UNANNOTATED;
CondVar mCondVar;
bool mWaiting;
virtual ~LargeAllocationFailureRunnable() { MOZ_ASSERT(!mWaiting); }
protected:
NS_IMETHOD Run() override {
MOZ_ASSERT(NS_IsMainThread());
XPCJSRuntime::Get()->OnLargeAllocationFailure();
MutexAutoLock lock(mMutex);
MOZ_ASSERT(mWaiting);
mWaiting = false;
mCondVar.Notify();
return NS_OK;
}
public:
LargeAllocationFailureRunnable()
: mozilla::Runnable("LargeAllocationFailureRunnable"),
mMutex("LargeAllocationFailureRunnable::mMutex"),
mCondVar(mMutex, "LargeAllocationFailureRunnable::mCondVar"),
mWaiting(true) {
MOZ_ASSERT(!NS_IsMainThread());
}
void BlockUntilDone() {
MOZ_ASSERT(!NS_IsMainThread());
MutexAutoLock lock(mMutex);
while (mWaiting) {
mCondVar.Wait();
}
}
};
static void OnLargeAllocationFailureCallback() {
// This callback can be called from any thread, including internal JS helper
// and DOM worker threads. We need to send the low-memory event via the
// observer service which can only be called on the main thread, so proxy to
// the main thread if we're not there already. The purpose of this callback
// is to synchronously free some memory so the caller can retry a failed
// allocation, so block on the completion.
if (NS_IsMainThread()) {
XPCJSRuntime::Get()->OnLargeAllocationFailure();
return;
}
RefPtr<LargeAllocationFailureRunnable> r = new LargeAllocationFailureRunnable;
if (NS_WARN_IF(NS_FAILED(NS_DispatchToMainThread(r)))) {
return;
}
r->BlockUntilDone();
}
// Usually this is used through nsIPlatformInfo. However, being able to query
// this interface on all threads risk triggering some main-thread assertions
// which is not guaranteed by the callers of GetBuildId.
extern const char gToolkitBuildID[];
bool mozilla::GetBuildId(JS::BuildIdCharVector* aBuildID) {
size_t length = std::char_traits<char>::length(gToolkitBuildID);
return aBuildID->append(gToolkitBuildID, length);
}
size_t XPCJSRuntime::SizeOfIncludingThis(MallocSizeOf mallocSizeOf) {
size_t n = 0;
n += mallocSizeOf(this);
n += mWrappedJSMap->SizeOfIncludingThis(mallocSizeOf);
n += mIID2NativeInterfaceMap->SizeOfIncludingThis(mallocSizeOf);
n += mClassInfo2NativeSetMap->ShallowSizeOfIncludingThis(mallocSizeOf);
n += mNativeSetMap->SizeOfIncludingThis(mallocSizeOf);
n += CycleCollectedJSRuntime::SizeOfExcludingThis(mallocSizeOf);
// There are other XPCJSRuntime members that could be measured; the above
// ones have been seen by DMD to be worth measuring. More stuff may be
// added later.
return n;
}
size_t CompartmentPrivate::SizeOfIncludingThis(MallocSizeOf mallocSizeOf) {
size_t n = mallocSizeOf(this);
n += mWrappedJSMap->SizeOfIncludingThis(mallocSizeOf);
n += mWrappedJSMap->SizeOfWrappedJS(mallocSizeOf);
return n;
}
/***************************************************************************/
void XPCJSRuntime::Shutdown(JSContext* cx) {
// This destructor runs before ~CycleCollectedJSContext, which does the actual
// JS_DestroyContext() call. But destroying the context triggers one final GC,
// which can call back into the context with various callbacks if we aren't
// careful. Remove the relevant callbacks, but leave the weak pointer
// callbacks to clear out any remaining table entries.
JS_RemoveFinalizeCallback(cx, FinalizeCallback);
xpc_DelocalizeRuntime(JS_GetRuntime(cx));
JS::SetGCSliceCallback(cx, mPrevGCSliceCallback);
nsScriptSecurityManager::ClearJSCallbacks(cx);
// Clean up and destroy maps. Any remaining entries in mWrappedJSMap will be
// cleaned up by the weak pointer callbacks.
mIID2NativeInterfaceMap = nullptr;
mClassInfo2NativeSetMap = nullptr;
mNativeSetMap = nullptr;
// Prevent ~LinkedList assertion failures if we leaked things.
mWrappedNativeScopes.clear();
mSubjectToFinalizationWJS.clear();
CycleCollectedJSRuntime::Shutdown(cx);
}
XPCJSRuntime::~XPCJSRuntime() {
MOZ_COUNT_DTOR_INHERITED(XPCJSRuntime, CycleCollectedJSRuntime);
}
// If |*anonymizeID| is non-zero and this is a user realm, the name will
// be anonymized.
static void GetRealmName(JS::Realm* realm, nsCString& name, int* anonymizeID,
bool replaceSlashes) {
if (*anonymizeID && !js::IsSystemRealm(realm)) {
name.AppendPrintf("<anonymized-%d>", *anonymizeID);
*anonymizeID += 1;
} else if (JSPrincipals* principals = JS::GetRealmPrincipals(realm)) {
nsresult rv = nsJSPrincipals::get(principals)->GetScriptLocation(name);
if (NS_FAILED(rv)) {
name.AssignLiteral("(unknown)");
}
// If the realm's location (name) differs from the principal's script
// location, append the realm's location to allow differentiation of
// multiple realms owned by the same principal (e.g. components owned
// by the system or null principal).
RealmPrivate* realmPrivate = RealmPrivate::Get(realm);
if (realmPrivate) {
const nsACString& location = realmPrivate->GetLocation();
if (!location.IsEmpty() && !location.Equals(name)) {
name.AppendLiteral(", ");
name.Append(location);
}
}
if (*anonymizeID) {
// We might have a file:// URL that includes a path from the local
// filesystem, which should be omitted if we're anonymizing.
static const char* filePrefix = "file://";
int filePos = name.Find(filePrefix);
if (filePos >= 0) {
int pathPos = filePos + strlen(filePrefix);
int lastSlashPos = -1;
for (int i = pathPos; i < int(name.Length()); i++) {
if (name[i] == '/' || name[i] == '\\') {
lastSlashPos = i;
}
}
if (lastSlashPos != -1) {
name.ReplaceLiteral(pathPos, lastSlashPos - pathPos, "<anonymized>");
} else {
// Something went wrong. Anonymize the entire path to be
// safe.
name.Truncate(pathPos);
name += "<anonymized?!>";
}
}
// We might have a location like this:
// The owner should be omitted if it's not a chrome: URI and we're
// anonymizing.
static const char* ownedByPrefix = "inProcessBrowserChildGlobal?ownedBy=";
int ownedByPos = name.Find(ownedByPrefix);
if (ownedByPos >= 0) {
const char* chrome = "chrome:";
int ownerPos = ownedByPos + strlen(ownedByPrefix);
const nsDependentCSubstring& ownerFirstPart =
Substring(name, ownerPos, strlen(chrome));
if (!ownerFirstPart.EqualsASCII(chrome)) {
name.Truncate(ownerPos);
name += "<anonymized>";
}
}
}
// A hack: replace forward slashes with '\\' so they aren't
// treated as path separators. Users of the reporters
// (such as about:memory) have to undo this change.
if (replaceSlashes) {
name.ReplaceChar('/', '\\');
}
} else {
name.AssignLiteral("null-principal");
}
}
extern void xpc::GetCurrentRealmName(JSContext* cx, nsCString& name) {
RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
if (!global) {
name.AssignLiteral("no global");
return;
}
JS::Realm* realm = GetNonCCWObjectRealm(global);
int anonymizeID = 0;
GetRealmName(realm, name, &anonymizeID, false);
}
void xpc::AddGCCallback(xpcGCCallback cb) {
XPCJSRuntime::Get()->AddGCCallback(cb);
}
void xpc::RemoveGCCallback(xpcGCCallback cb) {
XPCJSRuntime::Get()->RemoveGCCallback(cb);
}
static int64_t JSMainRuntimeGCHeapDistinguishedAmount() {
JSContext* cx = danger::GetJSContext();
return int64_t(JS_GetGCParameter(cx, JSGC_TOTAL_CHUNKS)) * js::gc::ChunkSize;
}
static int64_t JSMainRuntimeTemporaryPeakDistinguishedAmount() {
JSContext* cx = danger::GetJSContext();
return JS::PeakSizeOfTemporary(cx);
}
static int64_t JSMainRuntimeCompartmentsSystemDistinguishedAmount() {
JSContext* cx = danger::GetJSContext();
return JS::SystemCompartmentCount(cx);
}
static int64_t JSMainRuntimeCompartmentsUserDistinguishedAmount() {
JSContext* cx = XPCJSContext::Get()->Context();
return JS::UserCompartmentCount(cx);
}
static int64_t JSMainRuntimeRealmsSystemDistinguishedAmount() {
JSContext* cx = danger::GetJSContext();
return JS::SystemRealmCount(cx);
}
static int64_t JSMainRuntimeRealmsUserDistinguishedAmount() {
JSContext* cx = XPCJSContext::Get()->Context();
return JS::UserRealmCount(cx);
}
class JSMainRuntimeTemporaryPeakReporter final : public nsIMemoryReporter {
~JSMainRuntimeTemporaryPeakReporter() = default;
public:
NS_DECL_ISUPPORTS
NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
nsISupports* aData, bool aAnonymize) override {
MOZ_COLLECT_REPORT(
"js-main-runtime-temporary-peak", KIND_OTHER, UNITS_BYTES,
JSMainRuntimeTemporaryPeakDistinguishedAmount(),
"Peak transient data size in the main JSRuntime (the current size "
"of which is reported as "
"'explicit/js-non-window/runtime/temporary').");
return NS_OK;
}
};
NS_IMPL_ISUPPORTS(JSMainRuntimeTemporaryPeakReporter, nsIMemoryReporter)
// The REPORT* macros do an unconditional report. The ZRREPORT* macros are for
// realms and zones; they aggregate any entries smaller than
// SUNDRIES_THRESHOLD into the "sundries/gc-heap" and "sundries/malloc-heap"
// entries for the realm.
#define SUNDRIES_THRESHOLD js::MemoryReportingSundriesThreshold()
#define REPORT(_path, _kind, _units, _amount, _desc) \
handleReport->Callback(""_ns, _path, nsIMemoryReporter::_kind, \
nsIMemoryReporter::_units, _amount, \
nsLiteralCString(_desc), data);
#define REPORT_BYTES(_path, _kind, _amount, _desc) \
REPORT(_path, _kind, UNITS_BYTES, _amount, _desc);
#define REPORT_GC_BYTES(_path, _amount, _desc) \
do { \
size_t amount = _amount; /* evaluate _amount only once */ \
handleReport->Callback(""_ns, _path, nsIMemoryReporter::KIND_NONHEAP, \
nsIMemoryReporter::UNITS_BYTES, amount, \
nsLiteralCString(_desc), data); \
gcTotal += amount; \
} while (0)
// Report realm/zone non-GC (KIND_HEAP) bytes.
#define ZRREPORT_BYTES(_path, _amount, _desc) \
do { \
/* Assign _descLiteral plus "" into a char* to prove that it's */ \
/* actually a literal. */ \
size_t amount = _amount; /* evaluate _amount only once */ \
if (amount >= SUNDRIES_THRESHOLD) { \
handleReport->Callback(""_ns, _path, nsIMemoryReporter::KIND_HEAP, \
nsIMemoryReporter::UNITS_BYTES, amount, \
nsLiteralCString(_desc), data); \
} else { \
sundriesMallocHeap += amount; \
} \
} while (0)
// Report realm/zone GC bytes.
#define ZRREPORT_GC_BYTES(_path, _amount, _desc) \
do { \
size_t amount = _amount; /* evaluate _amount only once */ \
if (amount >= SUNDRIES_THRESHOLD) { \
handleReport->Callback(""_ns, _path, nsIMemoryReporter::KIND_NONHEAP, \
nsIMemoryReporter::UNITS_BYTES, amount, \
nsLiteralCString(_desc), data); \
gcTotal += amount; \
} else { \
sundriesGCHeap += amount; \
} \
} while (0)
// Report realm/zone non-heap bytes.
#define ZRREPORT_NONHEAP_BYTES(_path, _amount, _desc) \
do { \
size_t amount = _amount; /* evaluate _amount only once */ \
if (amount >= SUNDRIES_THRESHOLD) { \
handleReport->Callback(""_ns, _path, nsIMemoryReporter::KIND_NONHEAP, \
nsIMemoryReporter::UNITS_BYTES, amount, \
nsLiteralCString(_desc), data); \
} else { \
sundriesNonHeap += amount; \
} \
} while (0)
// Report runtime bytes.
#define RREPORT_BYTES(_path, _kind, _amount, _desc) \
do { \
size_t amount = _amount; /* evaluate _amount only once */ \
handleReport->Callback(""_ns, _path, nsIMemoryReporter::_kind, \
nsIMemoryReporter::UNITS_BYTES, amount, \
nsLiteralCString(_desc), data); \
rtTotal += amount; \
} while (0)
// Report GC thing bytes.
#define MREPORT_BYTES(_path, _kind, _amount, _desc) \
do { \
size_t amount = _amount; /* evaluate _amount only once */ \
handleReport->Callback(""_ns, _path, nsIMemoryReporter::_kind, \
nsIMemoryReporter::UNITS_BYTES, amount, \
nsLiteralCString(_desc), data); \
gcThingTotal += amount; \
} while (0)
MOZ_DEFINE_MALLOC_SIZE_OF(JSMallocSizeOf)
namespace xpc {
static void ReportZoneStats(const JS::ZoneStats& zStats,
const xpc::ZoneStatsExtras& extras,
nsIHandleReportCallback* handleReport,
nsISupports* data, bool anonymize,
size_t* gcTotalOut = nullptr) {
const nsCString& pathPrefix = extras.pathPrefix;
size_t gcTotal = 0;
size_t sundriesGCHeap = 0;
size_t sundriesMallocHeap = 0;
size_t sundriesNonHeap = 0;
MOZ_ASSERT(!gcTotalOut == zStats.isTotals);
ZRREPORT_GC_BYTES(pathPrefix + "symbols/gc-heap"_ns, zStats.symbolsGCHeap,
"Symbols.");
ZRREPORT_GC_BYTES(
pathPrefix + "gc-heap-arena-admin"_ns, zStats.gcHeapArenaAdmin,
"Bookkeeping information and alignment padding within GC arenas.");
ZRREPORT_GC_BYTES(pathPrefix + "unused-gc-things"_ns,
zStats.unusedGCThings.totalSize(),
"Unused GC thing cells within non-empty arenas.");
ZRREPORT_BYTES(pathPrefix + "unique-id-map"_ns, zStats.uniqueIdMap,
"Address-independent cell identities.");
ZRREPORT_BYTES(pathPrefix + "propmap-tables"_ns, zStats.initialPropMapTable,
"Tables storing property map information.");
ZRREPORT_BYTES(pathPrefix + "shape-tables"_ns, zStats.shapeTables,
"Tables storing shape information.");
ZRREPORT_BYTES(pathPrefix + "compartments/compartment-objects"_ns,
zStats.compartmentObjects,
"The JS::Compartment objects in this zone.");
ZRREPORT_BYTES(
pathPrefix + "compartments/cross-compartment-wrapper-tables"_ns,
zStats.crossCompartmentWrappersTables,
"The cross-compartment wrapper tables.");
ZRREPORT_BYTES(
pathPrefix + "compartments/private-data"_ns,
zStats.compartmentsPrivateData,
"Extra data attached to each compartment by XPConnect, including "
"its wrapped-js.");
ZRREPORT_GC_BYTES(pathPrefix + "jit-codes-gc-heap"_ns, zStats.jitCodesGCHeap,
"References to executable code pools used by the JITs.");
ZRREPORT_GC_BYTES(pathPrefix + "getter-setters-gc-heap"_ns,
zStats.getterSettersGCHeap,
"Information for getter/setter properties.");
ZRREPORT_GC_BYTES(pathPrefix + "property-maps/gc-heap/compact"_ns,
zStats.compactPropMapsGCHeap,
"Information about object properties.");
ZRREPORT_GC_BYTES(pathPrefix + "property-maps/gc-heap/normal"_ns,
zStats.normalPropMapsGCHeap,
"Information about object properties.");
ZRREPORT_GC_BYTES(pathPrefix + "property-maps/gc-heap/dict"_ns,
zStats.dictPropMapsGCHeap,
"Information about dictionary mode object properties.");
ZRREPORT_BYTES(pathPrefix + "property-maps/malloc-heap/children"_ns,
zStats.propMapChildren, "Tables for PropMap children.");
ZRREPORT_BYTES(pathPrefix + "property-maps/malloc-heap/tables"_ns,
zStats.propMapTables, "HashTables for PropMaps.");
ZRREPORT_GC_BYTES(pathPrefix + "scopes/gc-heap"_ns, zStats.scopesGCHeap,
"Scope information for scripts.");
ZRREPORT_BYTES(pathPrefix + "scopes/malloc-heap"_ns, zStats.scopesMallocHeap,
"Arrays of binding names and other binding-related data.");
ZRREPORT_GC_BYTES(pathPrefix + "regexp-shareds/gc-heap"_ns,
zStats.regExpSharedsGCHeap, "Shared compiled regexp data.");
ZRREPORT_BYTES(pathPrefix + "regexp-shareds/malloc-heap"_ns,
zStats.regExpSharedsMallocHeap,
"Shared compiled regexp data.");
ZRREPORT_BYTES(pathPrefix + "regexp-zone"_ns, zStats.regexpZone,
"The regexp zone and regexp data.");
ZRREPORT_BYTES(pathPrefix + "jit-zone"_ns, zStats.jitZone, "The JIT zone.");
ZRREPORT_BYTES(pathPrefix + "baseline/optimized-stubs"_ns,
zStats.baselineStubsOptimized,
"The Baseline JIT's optimized IC stubs (excluding code).");
ZRREPORT_BYTES(pathPrefix + "script-counts-map"_ns, zStats.scriptCountsMap,
"Profiling-related information for scripts.");
ZRREPORT_NONHEAP_BYTES(pathPrefix + "code/ion"_ns, zStats.code.ion,
"Code generated by the IonMonkey JIT.");
ZRREPORT_NONHEAP_BYTES(pathPrefix + "code/baseline"_ns, zStats.code.baseline,
"Code generated by the Baseline JIT.");
ZRREPORT_NONHEAP_BYTES(pathPrefix + "code/regexp"_ns, zStats.code.regexp,
"Code generated by the regexp JIT.");
ZRREPORT_NONHEAP_BYTES(
pathPrefix + "code/other"_ns, zStats.code.other,
"Code generated by the JITs for wrappers and trampolines.");
ZRREPORT_NONHEAP_BYTES(pathPrefix + "code/unused"_ns, zStats.code.unused,
"Memory allocated by one of the JITs to hold code, "
"but which is currently unused.");
size_t stringsNotableAboutMemoryGCHeap = 0;
size_t stringsNotableAboutMemoryMallocHeap = 0;
#define MAYBE_INLINE "The characters may be inline or on the malloc heap."
#define MAYBE_OVERALLOCATED \
"Sometimes over-allocated to simplify string concatenation."
for (size_t i = 0; i < zStats.notableStrings.length(); i++) {
const JS::NotableStringInfo& info = zStats.notableStrings[i];
MOZ_ASSERT(!zStats.isTotals);
// We don't do notable string detection when anonymizing, because
// there's a good chance its for crash submission, and the memory
// required for notable string detection is high.
MOZ_ASSERT(!anonymize);
nsDependentCString notableString(info.buffer.get());
// Viewing about:memory generates many notable strings which contain
// "string(length=". If we report these as notable, then we'll create
// even more notable strings the next time we open about:memory (unless
// there's a GC in the meantime), and so on ad infinitum.
//
// To avoid cluttering up about:memory like this, we stick notable
// strings which contain "string(length=" into their own bucket.
#define STRING_LENGTH "string(length="
if (FindInReadable(nsLiteralCString(STRING_LENGTH), notableString)) {
stringsNotableAboutMemoryGCHeap += info.gcHeapLatin1;
stringsNotableAboutMemoryGCHeap += info.gcHeapTwoByte;
stringsNotableAboutMemoryMallocHeap += info.mallocHeapLatin1;
stringsNotableAboutMemoryMallocHeap += info.mallocHeapTwoByte;
continue;
}
// Escape / to \ before we put notableString into the memory reporter
// path, because we don't want any forward slashes in the string to
// count as path separators.
nsCString escapedString(notableString);
escapedString.ReplaceSubstring("/", "\\");
bool truncated = notableString.Length() < info.length;
nsCString path =
pathPrefix +
nsPrintfCString("strings/" STRING_LENGTH "%zu, copies=%d, \"%s\"%s)/",
info.length, info.numCopies, escapedString.get(),
truncated ? " (truncated)" : "");
if (info.gcHeapLatin1 > 0) {
REPORT_GC_BYTES(path + "gc-heap/latin1"_ns, info.gcHeapLatin1,
"Latin1 strings. " MAYBE_INLINE);
}
if (info.gcHeapTwoByte > 0) {
REPORT_GC_BYTES(path + "gc-heap/two-byte"_ns, info.gcHeapTwoByte,
"TwoByte strings. " MAYBE_INLINE);
}
if (info.mallocHeapLatin1 > 0) {
REPORT_BYTES(path + "malloc-heap/latin1"_ns, KIND_HEAP,
info.mallocHeapLatin1,
"Non-inline Latin1 string characters. " MAYBE_OVERALLOCATED);
}
if (info.mallocHeapTwoByte > 0) {
REPORT_BYTES(
path + "malloc-heap/two-byte"_ns, KIND_HEAP, info.mallocHeapTwoByte,
"Non-inline TwoByte string characters. " MAYBE_OVERALLOCATED);
}
}
nsCString nonNotablePath = pathPrefix;
nonNotablePath += (zStats.isTotals || anonymize)
? "strings/"_ns
: "strings/string(<non-notable strings>)/"_ns;
if (zStats.stringInfo.gcHeapLatin1 > 0) {
REPORT_GC_BYTES(nonNotablePath + "gc-heap/latin1"_ns,
zStats.stringInfo.gcHeapLatin1,
"Latin1 strings. " MAYBE_INLINE);
}
if (zStats.stringInfo.gcHeapTwoByte > 0) {
REPORT_GC_BYTES(nonNotablePath + "gc-heap/two-byte"_ns,