Mirror of roytam1's UXP fork just in case Moonchild and Tobin decide to go after him
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

3739 lines
138 KiB

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim: set ts=8 sts=4 et sw=4 tw=99: */
/* 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
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* Per JSContext object */
#include "mozilla/MemoryReporting.h"
#include "mozilla/UniquePtr.h"
#include "xpcprivate.h"
#include "xpcpublic.h"
#include "XPCWrapper.h"
#include "XPCJSMemoryReporter.h"
#include "WrapperFactory.h"
#include "mozJSComponentLoader.h"
#include "nsAutoPtr.h"
#include "nsNetUtil.h"
#include "nsIMemoryInfoDumper.h"
#include "nsIMemoryReporter.h"
#include "nsIObserverService.h"
#include "nsIDebug2.h"
#include "nsIDocShell.h"
#include "nsIRunnable.h"
#include "amIAddonManager.h"
#include "nsPIDOMWindow.h"
#include "nsPrintfCString.h"
#include "mozilla/Preferences.h"
#include "mozilla/Telemetry.h"
#include "mozilla/Services.h"
#include "mozilla/dom/ScriptSettings.h"
#include "nsContentUtils.h"
#include "nsCCUncollectableMarker.h"
#include "nsCycleCollectionNoteRootCallback.h"
#include "nsCycleCollector.h"
#include "nsScriptLoader.h"
#include "jsapi.h"
#include "jsprf.h"
#include "js/MemoryMetrics.h"
#include "mozilla/dom/GeneratedAtomList.h"
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/WindowBinding.h"
#include "mozilla/jsipc/CrossProcessObjectWrappers.h"
#include "mozilla/Atomics.h"
#include "mozilla/Attributes.h"
#include "mozilla/ProcessHangMonitor.h"
#include "mozilla/Sprintf.h"
#include "mozilla/UniquePtrExtensions.h"
#include "mozilla/Unused.h"
#include "AccessCheck.h"
#include "nsGlobalWindow.h"
#include "nsAboutProtocolUtils.h"
#include "GeckoProfiler.h"
#include "nsIXULRuntime.h"
#include "nsJSPrincipals.h"
#ifdef XP_WIN
#include <windows.h>
#endif
using namespace mozilla;
using namespace xpc;
using namespace JS;
using mozilla::dom::PerThreadAtomCache;
using mozilla::dom::AutoEntryScript;
/***************************************************************************/
const char* const XPCJSContext::mStrings[] = {
"constructor", // IDX_CONSTRUCTOR
"toString", // IDX_TO_STRING
"toSource", // IDX_TO_SOURCE
"lastResult", // IDX_LAST_RESULT
"returnCode", // IDX_RETURN_CODE
"value", // IDX_VALUE
"QueryInterface", // IDX_QUERY_INTERFACE
"Components", // IDX_COMPONENTS
"wrappedJSObject", // IDX_WRAPPED_JSOBJECT
"Object", // IDX_OBJECT
"Function", // IDX_FUNCTION
"prototype", // IDX_PROTOTYPE
"createInstance", // IDX_CREATE_INSTANCE
"item", // IDX_ITEM
"__proto__", // IDX_PROTO
"__iterator__", // IDX_ITERATOR
"__exposedProps__", // IDX_EXPOSEDPROPS
"eval", // IDX_EVAL
"controllers", // IDX_CONTROLLERS
"realFrameElement", // IDX_REALFRAMEELEMENT
"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
"lastIndex" // IDX_LASTINDEX
};
/***************************************************************************/
static mozilla::Atomic<bool> sDiscardSystemSource(false);
bool
xpc::ShouldDiscardSystemSource() { return sDiscardSystemSource; }
#ifdef DEBUG
static mozilla::Atomic<bool> sExtraWarningsForSystemJS(false);
bool xpc::ExtraWarningsForSystemJS() { return sExtraWarningsForSystemJS; }
#else
bool xpc::ExtraWarningsForSystemJS() { return false; }
#endif
static mozilla::Atomic<bool> sSharedMemoryEnabled(false);
bool
xpc::SharedMemoryEnabled() { return sSharedMemoryEnabled; }
// *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
{
TimeStamp start = TimeStamp::Now();
bool hadSnowWhiteObjects = nsCycleCollector_doDeferredDeletion();
Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_ASYNC_SNOW_WHITE_FREEING,
uint32_t((TimeStamp::Now() - start).ToMilliseconds()));
if (hadSnowWhiteObjects && !mContinuation) {
mContinuation = true;
if (NS_FAILED(NS_DispatchToCurrentThread(this))) {
mActive = false;
}
} else {
mActive = false;
}
return NS_OK;
}
void Dispatch(bool aContinuation = false, bool aPurge = false)
{
if (mContinuation) {
mContinuation = aContinuation;
}
mPurge = aPurge;
if (!mActive && NS_SUCCEEDED(NS_DispatchToCurrentThread(this))) {
mActive = true;
}
}
AsyncFreeSnowWhite() : mContinuation(false), mActive(false), mPurge(false) {}
public:
bool mContinuation;
bool mActive;
bool mPurge;
};
namespace xpc {
CompartmentPrivate::CompartmentPrivate(JSCompartment* c)
: wantXrays(false)
, allowWaivers(true)
, writeToGlobalPrototype(false)
, skipWriteToGlobalPrototype(false)
, isWebExtensionContentScript(false)
, waiveInterposition(false)
, allowCPOWs(false)
, universalXPConnectEnabled(false)
, forcePermissiveCOWs(false)
, scriptability(c)
, scope(nullptr)
, mWrappedJSMap(JSObject2WrappedJSMap::newMap(XPC_JS_MAP_LENGTH))
{
MOZ_COUNT_CTOR(xpc::CompartmentPrivate);
mozilla::PodArrayZero(wrapperDenialWarnings);
}
CompartmentPrivate::~CompartmentPrivate()
{
MOZ_COUNT_DTOR(xpc::CompartmentPrivate);
mWrappedJSMap->ShutdownMarker();
delete mWrappedJSMap;
}
static bool
TryParseLocationURICandidate(const nsACString& uristr,
CompartmentPrivate::LocationHint aLocationHint,
nsIURI** aURI)
{
static NS_NAMED_LITERAL_CSTRING(kGRE, "resource://gre/");
static NS_NAMED_LITERAL_CSTRING(kToolkit, "chrome://global/");
static NS_NAMED_LITERAL_CSTRING(kBrowser, "chrome://browser/");
if (aLocationHint == CompartmentPrivate::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, NS_LITERAL_CSTRING("xb")))
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 CompartmentPrivate::TryParseLocationURI(CompartmentPrivate::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:
// <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.
// <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, such as the add-on SDK.
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_LITERAL_CSTRING(":"));
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 (nsXPConnect::SecurityManager()->IsSystemPrincipal(aPrincipal))
return true;
// nsExpandedPrincipal gets a free pass.
nsCOMPtr<nsIExpandedPrincipal> ep = do_QueryInterface(aPrincipal);
if (ep)
return true;
// Check whether our URI is an "about:" URI that allows scripts. If it is,
// we need to allow JS to run.
nsCOMPtr<nsIURI> principalURI;
aPrincipal->GetURI(getter_AddRefs(principalURI));
MOZ_ASSERT(principalURI);
bool isAbout;
nsresult rv = principalURI->SchemeIs("about", &isAbout);
if (NS_SUCCEEDED(rv) && isAbout) {
nsCOMPtr<nsIAboutModule> module;
rv = NS_GetAboutModule(principalURI, getter_AddRefs(module));
if (NS_SUCCEEDED(rv)) {
uint32_t flags;
rv = module->GetURIFlags(principalURI, &flags);
if (NS_SUCCEEDED(rv) &&
(flags & nsIAboutModule::ALLOW_SCRIPT)) {
return true;
}
}
}
return false;
}
Scriptability::Scriptability(JSCompartment* c) : mScriptBlocks(0)
, mDocShellAllowsScript(true)
, mScriptBlockedByPolicy(false)
{
nsIPrincipal* prin = nsJSPrincipals::get(JS_GetCompartmentPrincipals(c));
mImmuneToScriptPolicy = PrincipalImmuneToScriptPolicy(prin);
// If we're not immune, we should have a real principal with a codebase URI.
// Check the URI against the new-style domain policy.
if (!mImmuneToScriptPolicy) {
nsCOMPtr<nsIURI> codebase;
nsresult rv = prin->GetURI(getter_AddRefs(codebase));
bool policyAllows;
if (NS_SUCCEEDED(rv) && codebase &&
NS_SUCCEEDED(nsXPConnect::SecurityManager()->PolicyAllowsScript(codebase, &policyAllows)))
{
mScriptBlockedByPolicy = !policyAllows;
} else {
// Something went wrong - be safe and block script.
mScriptBlockedByPolicy = true;
}
}
}
bool
Scriptability::Allowed()
{
return mDocShellAllowsScript && !mScriptBlockedByPolicy &&
mScriptBlocks == 0;
}
bool
Scriptability::IsImmuneToScriptPolicy()
{
return mImmuneToScriptPolicy;
}
void
Scriptability::Block()
{
++mScriptBlocks;
}
void
Scriptability::Unblock()
{
MOZ_ASSERT(mScriptBlocks > 0);
--mScriptBlocks;
}
void
Scriptability::SetDocShellAllowsScript(bool aAllowed)
{
mDocShellAllowsScript = aAllowed || mImmuneToScriptPolicy;
}
/* static */
Scriptability&
Scriptability::Get(JSObject* aScope)
{
return CompartmentPrivate::Get(aScope)->scriptability;
}
bool
IsContentXBLScope(JSCompartment* compartment)
{
// We always eagerly create compartment privates for XBL scopes.
CompartmentPrivate* priv = CompartmentPrivate::Get(compartment);
if (!priv || !priv->scope)
return false;
return priv->scope->IsContentXBLScope();
}
bool
IsInContentXBLScope(JSObject* obj)
{
return IsContentXBLScope(js::GetObjectCompartment(obj));
}
bool
IsInAddonScope(JSObject* obj)
{
return ObjectScope(obj)->IsAddonScope();
}
bool
IsUniversalXPConnectEnabled(JSCompartment* compartment)
{
CompartmentPrivate* priv = CompartmentPrivate::Get(compartment);
if (!priv)
return false;
return priv->universalXPConnectEnabled;
}
bool
IsUniversalXPConnectEnabled(JSContext* cx)
{
JSCompartment* compartment = js::GetContextCompartment(cx);
if (!compartment)
return false;
return IsUniversalXPConnectEnabled(compartment);
}
bool
EnableUniversalXPConnect(JSContext* cx)
{
JSCompartment* compartment = js::GetContextCompartment(cx);
if (!compartment)
return true;
// Never set universalXPConnectEnabled on a chrome compartment - it confuses
// the security wrapping code.
if (AccessCheck::isChrome(compartment))
return true;
CompartmentPrivate* priv = CompartmentPrivate::Get(compartment);
if (!priv)
return true;
if (priv->universalXPConnectEnabled)
return true;
priv->universalXPConnectEnabled = true;
// Recompute all the cross-compartment wrappers leaving the newly-privileged
// compartment.
bool ok = js::RecomputeWrappers(cx, js::SingleCompartment(compartment),
js::AllCompartments());
NS_ENSURE_TRUE(ok, false);
// The Components object normally isn't defined for unprivileged web content,
// but we define it when UniversalXPConnect is enabled to support legacy
// tests.
XPCWrappedNativeScope* scope = priv->scope;
if (!scope)
return true;
scope->ForcePrivilegedComponents();
return scope->AttachComponentsObject(cx);
}
JSObject*
UnprivilegedJunkScope()
{
return XPCJSContext::Get()->UnprivilegedJunkScope();
}
JSObject*
PrivilegedJunkScope()
{
return XPCJSContext::Get()->PrivilegedJunkScope();
}
JSObject*
CompilationScope()
{
return XPCJSContext::Get()->CompilationScope();
}
nsGlobalWindow*
WindowOrNull(JSObject* aObj)
{
MOZ_ASSERT(aObj);
MOZ_ASSERT(!js::IsWrapper(aObj));
nsGlobalWindow* win = nullptr;
UNWRAP_NON_WRAPPER_OBJECT(Window, aObj, win);
return win;
}
nsGlobalWindow*
WindowGlobalOrNull(JSObject* aObj)
{
MOZ_ASSERT(aObj);
JSObject* glob = js::GetGlobalForObjectCrossCompartment(aObj);
return WindowOrNull(glob);
}
nsGlobalWindow*
AddonWindowOrNull(JSObject* aObj)
{
if (!IsInAddonScope(aObj))
return nullptr;
JSObject* global = js::GetGlobalForObjectCrossCompartment(aObj);
JSObject* proto = js::GetPrototypeNoProxy(global);
// Addons could theoretically change the prototype of the addon scope, but
// we pretty much just want to crash if that happens so that we find out
// about it and get them to change their code.
MOZ_RELEASE_ASSERT(js::IsCrossCompartmentWrapper(proto) ||
xpc::IsSandboxPrototypeProxy(proto));
JSObject* mainGlobal = js::UncheckedUnwrap(proto, /* stopAtWindowProxy = */ false);
MOZ_RELEASE_ASSERT(JS_IsGlobalObject(mainGlobal));
return WindowOrNull(mainGlobal);
}
nsGlobalWindow*
CurrentWindowOrNull(JSContext* cx)
{
JSObject* glob = JS::CurrentGlobalOrNull(cx);
return glob ? WindowOrNull(glob) : nullptr;
}
} // namespace xpc
static void
CompartmentDestroyedCallback(JSFreeOp* fop, JSCompartment* compartment)
{
// NB - This callback may be called in JS_DestroyContext, which happens
// after the XPCJSContext has been torn down.
// Get the current compartment private into an AutoPtr (which will do the
// cleanup for us), and null out the private (which may already be null).
nsAutoPtr<CompartmentPrivate> priv(CompartmentPrivate::Get(compartment));
JS_SetCompartmentPrivate(compartment, nullptr);
}
static size_t
CompartmentSizeOfIncludingThisCallback(MallocSizeOf mallocSizeOf, JSCompartment* 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 XPCJSContext::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
// bug 1277036.
return false;
}
void XPCJSContext::TraceNativeBlackRoots(JSTracer* trc)
{
if (AutoMarkingPtr* roots = Get()->mAutoRoots)
roots->TraceJSAll(trc);
// XPCJSObjectHolders don't participate in cycle collection, so always
// trace them here.
XPCRootSetElem* e;
for (e = mObjectHolderRoots; e; e = e->GetNextRoot())
static_cast<XPCJSObjectHolder*>(e)->TraceJS(trc);
dom::TraceBlackJS(trc, JS_GetGCParameter(Context(), JSGC_NUMBER),
nsXPConnect::XPConnect()->IsShuttingDown());
}
void XPCJSContext::TraceAdditionalNativeGrayRoots(JSTracer* trc)
{
XPCWrappedNativeScope::TraceWrappedNativesInAllScopes(trc, this);
for (XPCRootSetElem* e = mVariantRoots; e ; e = e->GetNextRoot())
static_cast<XPCTraceableVariant*>(e)->TraceJS(trc);
for (XPCRootSetElem* e = mWrappedJSRoots; e ; e = e->GetNextRoot())
static_cast<nsXPCWrappedJS*>(e)->TraceJS(trc);
}
void
XPCJSContext::TraverseAdditionalNativeRoots(nsCycleCollectionNoteRootCallback& cb)
{
XPCWrappedNativeScope::SuspectAllWrappers(this, cb);
for (XPCRootSetElem* e = mVariantRoots; e ; e = e->GetNextRoot()) {
XPCTraceableVariant* v = static_cast<XPCTraceableVariant*>(e);
if (nsCCUncollectableMarker::InGeneration(cb,
v->CCGeneration())) {
JS::Value val = v->GetJSValPreserveColor();
if (val.isObject() && !JS::ObjectIsMarkedGray(&val.toObject()))
continue;
}
cb.NoteXPCOMRoot(v);
}
for (XPCRootSetElem* e = mWrappedJSRoots; e ; e = e->GetNextRoot()) {
cb.NoteXPCOMRoot(ToSupports(static_cast<nsXPCWrappedJS*>(e)));
}
}
void
XPCJSContext::UnmarkSkippableJSHolders()
{
CycleCollectedJSContext::UnmarkSkippableJSHolders();
}
void
XPCJSContext::PrepareForForgetSkippable()
{
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
if (obs) {
obs->NotifyObservers(nullptr, "cycle-collector-forget-skippable", nullptr);
}
}
void
XPCJSContext::BeginCycleCollectionCallback()
{
nsJSContext::BeginCycleCollectionCallback();
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
if (obs) {
obs->NotifyObservers(nullptr, "cycle-collector-begin", nullptr);
}
}
void
XPCJSContext::EndCycleCollectionCallback(CycleCollectorResults& aResults)
{
nsJSContext::EndCycleCollectionCallback(aResults);
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
if (obs) {
obs->NotifyObservers(nullptr, "cycle-collector-end", nullptr);
}
}
void
XPCJSContext::DispatchDeferredDeletion(bool aContinuation, bool aPurge)
{
mAsyncSnowWhiteFreer->Dispatch(aContinuation, aPurge);
}
void
xpc_UnmarkSkippableJSHolders()
{
if (nsXPConnect::XPConnect()->GetContext()) {
nsXPConnect::XPConnect()->GetContext()->UnmarkSkippableJSHolders();
}
}
/* static */ void
XPCJSContext::GCSliceCallback(JSContext* cx,
JS::GCProgress progress,
const JS::GCDescription& desc)
{
XPCJSContext* self = nsXPConnect::GetContextInstance();
if (!self)
return;
if (self->mPrevGCSliceCallback)
(*self->mPrevGCSliceCallback)(cx, progress, desc);
}
/* static */ void
XPCJSContext::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.
NS_DispatchToCurrentThread(
NS_NewRunnableFunction([](){nsJSContext::CycleCollectNow(nullptr);}));
XPCJSContext* self = nsXPConnect::GetContextInstance();
if (!self)
return;
if (self->mPrevDoCycleCollectionCallback)
(*self->mPrevDoCycleCollectionCallback)(cx);
}
void
XPCJSContext::CustomGCCallback(JSGCStatus status)
{
nsTArray<xpcGCCallback> callbacks(extraGCCallbacks);
for (uint32_t i = 0; i < callbacks.Length(); ++i)
callbacks[i](status);
}
/* static */ void
XPCJSContext::FinalizeCallback(JSFreeOp* fop,
JSFinalizeStatus status,
bool isZoneGC,
void* data)
{
XPCJSContext* self = nsXPConnect::GetContextInstance();
if (!self)
return;
switch (status) {
case JSFINALIZE_GROUP_START:
{
MOZ_ASSERT(!self->mDoingFinalization, "bad state");
MOZ_ASSERT(!self->mGCIsRunning, "bad state");
self->mGCIsRunning = true;
self->mDoingFinalization = true;
break;
}
case JSFINALIZE_GROUP_END:
{
MOZ_ASSERT(self->mDoingFinalization, "bad state");
self->mDoingFinalization = false;
// Sweep scopes needing cleanup
XPCWrappedNativeScope::KillDyingScopes();
MOZ_ASSERT(self->mGCIsRunning, "bad state");
self->mGCIsRunning = false;
break;
}
case JSFINALIZE_COLLECTION_END:
{
MOZ_ASSERT(!self->mGCIsRunning, "bad state");
self->mGCIsRunning = true;
if (AutoMarkingPtr* roots = Get()->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 = XPCJSContext::Get()->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 transfered these native objects to this table when their
// JSObject's 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 where 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 where not themselves
// slated for finalization in this gc cycle. So... 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.
for (auto i = self->mDyingWrappedNativeProtoMap->Iter(); !i.Done(); i.Next()) {
auto entry = static_cast<XPCWrappedNativeProtoMap::Entry*>(i.Get());
delete static_cast<const XPCWrappedNativeProto*>(entry->key);
i.Remove();
}
MOZ_ASSERT(self->mGCIsRunning, "bad state");
self->mGCIsRunning = false;
break;
}
}
}
/* static */ void
XPCJSContext::WeakPointerZoneGroupCallback(JSContext* cx, 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.
XPCJSContext* self = static_cast<XPCJSContext*>(data);
self->mWrappedJSMap->UpdateWeakPointersAfterGC(self);
XPCWrappedNativeScope::UpdateWeakPointersAfterGC(self);
}
/* static */ void
XPCJSContext::WeakPointerCompartmentCallback(JSContext* cx, JSCompartment* comp, void* data)
{
// Called immediately after the ZoneGroup weak pointer callback, but only
// once for each compartment that is being swept.
XPCJSContext* self = static_cast<XPCJSContext*>(data);
CompartmentPrivate* xpcComp = CompartmentPrivate::Get(comp);
if (xpcComp)
xpcComp->UpdateWeakPointersAfterGC(self);
}
void
CompartmentPrivate::UpdateWeakPointersAfterGC(XPCJSContext* context)
{
mWrappedJSMap->UpdateWeakPointersAfterGC(context);
}
static void WatchdogMain(void* arg);
class Watchdog;
class WatchdogManager;
class AutoLockWatchdog {
Watchdog* const mWatchdog;
public:
explicit AutoLockWatchdog(Watchdog* aWatchdog);
~AutoLockWatchdog();
};
class Watchdog
{
public:
explicit Watchdog(WatchdogManager* aManager)
: mManager(aManager)
, mLock(nullptr)
, mWakeup(nullptr)
, mThread(nullptr)
, mHibernating(false)
, mInitialized(false)
, mShuttingDown(false)
, mMinScriptRunTimeSeconds(1)
{}
~Watchdog() { MOZ_ASSERT(!Initialized()); }
WatchdogManager* Manager() { return mManager; }
bool Initialized() { return mInitialized; }
bool ShuttingDown() { return mShuttingDown; }
PRLock* GetLock() { return mLock; }
bool Hibernating() { return mHibernating; }
void WakeUp()
{
MOZ_ASSERT(Initialized());
MOZ_ASSERT(Hibernating());
mHibernating = false;
PR_NotifyCondVar(mWakeup);
}
//
// Invoked by the main thread only.
//
void Init()
{
MOZ_ASSERT(NS_IsMainThread());
mLock = PR_NewLock();
if (!mLock)
NS_RUNTIMEABORT("PR_NewLock failed.");
mWakeup = PR_NewCondVar(mLock);
if (!mWakeup)
NS_RUNTIMEABORT("PR_NewCondVar failed.");
{
AutoLockWatchdog lock(this);
// Gecko uses thread private for accounting and has to clean up at thread exit.
// Therefore, even though we don't have a return value from the watchdog, we need to
// join it on shutdown.
mThread = PR_CreateThread(PR_USER_THREAD, WatchdogMain, this,
PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD,
PR_JOINABLE_THREAD, 0);
if (!mThread)
NS_RUNTIMEABORT("PR_CreateThread failed!");
// WatchdogMain acquires the lock and then asserts mInitialized. So
// make sure to set mInitialized before releasing the lock here so
// that it's atomic with the creation of the thread.
mInitialized = true;
}
}
void Shutdown()
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(Initialized());
{ // Scoped lock.
AutoLockWatchdog lock(this);
// Signal to the watchdog thread that it's time to shut down.
mShuttingDown = true;
// Wake up the watchdog, and wait for it to call us back.
PR_NotifyCondVar(mWakeup);
}
PR_JoinThread(mThread);
// The thread sets mShuttingDown to false as it exits.
MOZ_ASSERT(!mShuttingDown);
// Destroy state.
mThread = nullptr;
PR_DestroyCondVar(mWakeup);
mWakeup = nullptr;
PR_DestroyLock(mLock);
mLock = nullptr;
// All done.
mInitialized = false;
}
void SetMinScriptRunTimeSeconds(int32_t seconds)
{
// This variable is atomic, and is set from the main thread without
// locking.
MOZ_ASSERT(seconds > 0);
mMinScriptRunTimeSeconds = seconds;
}
//
// Invoked by the watchdog thread only.
//
void Hibernate()
{
MOZ_ASSERT(!NS_IsMainThread());
mHibernating = true;
Sleep(PR_INTERVAL_NO_TIMEOUT);
}
void Sleep(PRIntervalTime timeout)
{
MOZ_ASSERT(!NS_IsMainThread());
MOZ_ALWAYS_TRUE(PR_WaitCondVar(mWakeup, timeout) == PR_SUCCESS);
}
void Finished()
{
MOZ_ASSERT(!NS_IsMainThread());
mShuttingDown = false;
}
int32_t MinScriptRunTimeSeconds()
{
return mMinScriptRunTimeSeconds;
}
private:
WatchdogManager* mManager;
PRLock* mLock;
PRCondVar* mWakeup;
PRThread* mThread;
bool mHibernating;
bool mInitialized;
bool mShuttingDown;
mozilla::Atomic<int32_t> mMinScriptRunTimeSeconds;
};
#define PREF_MAX_SCRIPT_RUN_TIME_CONTENT "dom.max_script_run_time"
#define PREF_MAX_SCRIPT_RUN_TIME_CHROME "dom.max_chrome_script_run_time"
class WatchdogManager : public nsIObserver
{
public:
NS_DECL_ISUPPORTS
explicit WatchdogManager(XPCJSContext* aContext) : mContext(aContext)
, mContextState(CONTEXT_INACTIVE)
{
// All the timestamps start at zero except for context state change.
PodArrayZero(mTimestamps);
mTimestamps[TimestampContextStateChange] = PR_Now();
// Enable the watchdog, if appropriate.
RefreshWatchdog();
// Register ourselves as an observer to get updates on the pref.
mozilla::Preferences::AddStrongObserver(this, "dom.use_watchdog");
mozilla::Preferences::AddStrongObserver(this, PREF_MAX_SCRIPT_RUN_TIME_CONTENT);
mozilla::Preferences::AddStrongObserver(this, PREF_MAX_SCRIPT_RUN_TIME_CHROME);
}
protected:
virtual ~WatchdogManager()
{
// Shutting down the watchdog requires context-switching to the watchdog
// thread, which isn't great to do in a destructor. So we require
// consumers to shut it down manually before releasing it.
MOZ_ASSERT(!mWatchdog);
mozilla::Preferences::RemoveObserver(this, "dom.use_watchdog");
mozilla::Preferences::RemoveObserver(this, PREF_MAX_SCRIPT_RUN_TIME_CONTENT);
mozilla::Preferences::RemoveObserver(this, PREF_MAX_SCRIPT_RUN_TIME_CHROME);
}
public:
NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic,
const char16_t* aData) override
{
RefreshWatchdog();
return NS_OK;
}
// Context statistics. These live on the watchdog manager, are written
// from the main thread, and are read from the watchdog thread (holding
// the lock in each case).
void
RecordContextActivity(bool active)
{
// The watchdog reads this state, so acquire the lock before writing it.
MOZ_ASSERT(NS_IsMainThread());
Maybe<AutoLockWatchdog> lock;
if (mWatchdog)
lock.emplace(mWatchdog);
// Write state.
mTimestamps[TimestampContextStateChange] = PR_Now();
mContextState = active ? CONTEXT_ACTIVE : CONTEXT_INACTIVE;
// The watchdog may be hibernating, waiting for the context to go
// active. Wake it up if necessary.
if (active && mWatchdog && mWatchdog->Hibernating())
mWatchdog->WakeUp();
}
bool IsContextActive() { return mContextState == CONTEXT_ACTIVE; }
PRTime TimeSinceLastContextStateChange()
{
return PR_Now() - GetTimestamp(TimestampContextStateChange);
}
// Note - Because of the context activity timestamp, these are read and
// written from both threads.
void RecordTimestamp(WatchdogTimestampCategory aCategory)
{
// The watchdog thread always holds the lock when it runs.
Maybe<AutoLockWatchdog> maybeLock;
if (NS_IsMainThread() && mWatchdog)
maybeLock.emplace(mWatchdog);
mTimestamps[aCategory] = PR_Now();
}
PRTime GetTimestamp(WatchdogTimestampCategory aCategory)
{
// The watchdog thread always holds the lock when it runs.
Maybe<AutoLockWatchdog> maybeLock;
if (NS_IsMainThread() && mWatchdog)
maybeLock.emplace(mWatchdog);
return mTimestamps[aCategory];
}
XPCJSContext* Context() { return mContext; }
Watchdog* GetWatchdog() { return mWatchdog; }
void RefreshWatchdog()
{
bool wantWatchdog = Preferences::GetBool("dom.use_watchdog", true);
if (wantWatchdog != !!mWatchdog) {
if (wantWatchdog)
StartWatchdog();
else
StopWatchdog();
}
if (mWatchdog) {
int32_t contentTime = Preferences::GetInt(PREF_MAX_SCRIPT_RUN_TIME_CONTENT, 10);
if (contentTime <= 0)
contentTime = INT32_MAX;
int32_t chromeTime = Preferences::GetInt(PREF_MAX_SCRIPT_RUN_TIME_CHROME, 20);
if (chromeTime <= 0)
chromeTime = INT32_MAX;
mWatchdog->SetMinScriptRunTimeSeconds(std::min(contentTime, chromeTime));
}
}
void StartWatchdog()
{
MOZ_ASSERT(!mWatchdog);
mWatchdog = new Watchdog(this);
mWatchdog->Init();
}
void StopWatchdog()
{
MOZ_ASSERT(mWatchdog);
mWatchdog->Shutdown();
mWatchdog = nullptr;
}
private:
XPCJSContext* mContext;
nsAutoPtr<Watchdog> mWatchdog;
enum { CONTEXT_ACTIVE, CONTEXT_INACTIVE } mContextState;
PRTime mTimestamps[TimestampCount];
};
NS_IMPL_ISUPPORTS(WatchdogManager, nsIObserver)
AutoLockWatchdog::AutoLockWatchdog(Watchdog* aWatchdog) : mWatchdog(aWatchdog)
{
PR_Lock(mWatchdog->GetLock());
}
AutoLockWatchdog::~AutoLockWatchdog()
{
PR_Unlock(mWatchdog->GetLock());
}
static void
WatchdogMain(void* arg)
{
PR_SetCurrentThreadName("JS Watchdog");
Watchdog* self = static_cast<Watchdog*>(arg);
WatchdogManager* manager = self->Manager();
// Lock lasts until we return
AutoLockWatchdog lock(self);
MOZ_ASSERT(self->Initialized());
MOZ_ASSERT(!self->ShuttingDown());
while (!self->ShuttingDown()) {
// Sleep only 1 second if recently (or currently) active; otherwise, hibernate
if (manager->IsContextActive() ||
manager->TimeSinceLastContextStateChange() <= PRTime(2*PR_USEC_PER_SEC))
{
self->Sleep(PR_TicksPerSecond());
} else {
manager->RecordTimestamp(TimestampWatchdogHibernateStart);
self->Hibernate();
manager->RecordTimestamp(TimestampWatchdogHibernateStop);
}
// Rise and shine.
manager->RecordTimestamp(TimestampWatchdogWakeup);
// Don't request an interrupt callback unless the current script has
// been running long enough that we might show the slow script dialog.
// Triggering the callback from off the main thread can be expensive.
// We want to avoid showing the slow script dialog if the user's laptop
// goes to sleep in the middle of running a script. To ensure this, we
// invoke the interrupt callback after only half the timeout has
// elapsed. The callback simply records the fact that it was called in
// the mSlowScriptSecondHalf flag. Then we wait another (timeout/2)
// seconds and invoke the callback again. This time around it sees
// mSlowScriptSecondHalf is set and so it shows the slow script
// dialog. If the computer is put to sleep during one of the (timeout/2)
// periods, the script still has the other (timeout/2) seconds to
// finish.
PRTime usecs = self->MinScriptRunTimeSeconds() * PR_USEC_PER_SEC / 2;
if (manager->IsContextActive() &&
manager->TimeSinceLastContextStateChange() >= usecs)
{
bool debuggerAttached = false;
nsCOMPtr<nsIDebug2> dbg = do_GetService("@mozilla.org/xpcom/debug;1");
if (dbg)
dbg->GetIsDebuggerAttached(&debuggerAttached);
if (!debuggerAttached)
JS_RequestInterruptCallback(manager->Context()->Context());
}
}
// Tell the manager that we've shut down.
self->Finished();
}
PRTime
XPCJSContext::GetWatchdogTimestamp(WatchdogTimestampCategory aCategory)
{
return mWatchdogManager->GetTimestamp(aCategory);
}
void
xpc::SimulateActivityCallback(bool aActive)
{
XPCJSContext::ActivityCallback(XPCJSContext::Get(), aActive);
}
// static
void
XPCJSContext::ActivityCallback(void* arg, bool active)
{
if (!active) {
ProcessHangMonitor::ClearHang();
}
XPCJSContext* self = static_cast<XPCJSContext*>(arg);
self->mWatchdogManager->RecordContextActivity(active);
}
// static
bool
XPCJSContext::InterruptCallback(JSContext* cx)
{
XPCJSContext* self = XPCJSContext::Get();
// Normally we record mSlowScriptCheckpoint when we start to process an
// event. However, we can run JS outside of event handlers. This code takes
// care of that case.
if (self->mSlowScriptCheckpoint.IsNull()) {
self->mSlowScriptCheckpoint = TimeStamp::NowLoRes();
self->mSlowScriptSecondHalf = false;
self->mSlowScriptActualWait = mozilla::TimeDuration();
self->mTimeoutAccumulated = false;
return true;
}
// Sometimes we get called back during XPConnect initialization, before Gecko
// has finished bootstrapping. Avoid crashing in nsContentUtils below.
if (!nsContentUtils::IsInitialized())
return true;
// This is at least the second interrupt callback we've received since
// returning to the event loop. See how long it's been, and what the limit
// is.
TimeDuration duration = TimeStamp::NowLoRes() - self->mSlowScriptCheckpoint;
bool chrome = nsContentUtils::IsCallerChrome();
const char* prefName = chrome ? PREF_MAX_SCRIPT_RUN_TIME_CHROME
: PREF_MAX_SCRIPT_RUN_TIME_CONTENT;
int32_t limit = Preferences::GetInt(prefName, chrome ? 20 : 10);
// If there's no limit, or we're within the limit, let it go.
if (limit == 0 || duration.ToSeconds() < limit / 2.0)
return true;
self->mSlowScriptActualWait += duration;
// In order to guard against time changes or laptops going to sleep, we
// don't trigger the slow script warning until (limit/2) seconds have
// elapsed twice.
if (!self->mSlowScriptSecondHalf) {
self->mSlowScriptCheckpoint = TimeStamp::NowLoRes();
self->mSlowScriptSecondHalf = true;
return true;
}
//
// This has gone on long enough! Time to take action. ;-)
//
// Get the DOM window associated with the running script. If the script is
// running in a non-DOM scope, we have to just let it keep running.
RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
RefPtr<nsGlobalWindow> win = WindowOrNull(global);
if (!win && IsSandbox(global)) {
// If this is a sandbox associated with a DOMWindow via a
// sandboxPrototype, use that DOMWindow. This supports GreaseMonkey
// and JetPack content scripts.
JS::Rooted<JSObject*> proto(cx);
if (!JS_GetPrototype(cx, global, &proto))
return false;
if (proto && IsSandboxPrototypeProxy(proto) &&
(proto = js::CheckedUnwrap(proto, /* stopAtWindowProxy = */ false)))
{
win = WindowGlobalOrNull(proto);
}
}
if (!win) {
NS_WARNING("No active window");
return true;
}
if (win->IsDying()) {
// The window is being torn down. When that happens we try to prevent
// the dispatch of new runnables, so it also makes sense to kill any
// long-running script. The user is primarily interested in this page
// going away.
return false;
}
if (win->GetIsPrerendered()) {
// We cannot display a dialog if the page is being prerendered, so
// just kill the page.
mozilla::dom::HandlePrerenderingViolation(win->AsInner());
return false;
}
// Accumulate slow script invokation delay.
if (!chrome && !self->mTimeoutAccumulated) {
uint32_t delay = uint32_t(self->mSlowScriptActualWait.ToMilliseconds() - (limit * 1000.0));
Telemetry::Accumulate(Telemetry::SLOW_SCRIPT_NOTIFY_DELAY, delay);
self->mTimeoutAccumulated = true;
}
// Show the prompt to the user, and kill if requested.
nsGlobalWindow::SlowScriptResponse response = win->ShowSlowScriptDialog();
if (response == nsGlobalWindow::KillSlowScript) {
if (Preferences::GetBool("dom.global_stop_script", true))
xpc::Scriptability::Get(global).Block();
return false;
}
// The user chose to continue the script. Reset the timer, and disable this
// machinery with a pref of the user opted out of future slow-script dialogs.
if (response != nsGlobalWindow::ContinueSlowScriptAndKeepNotifying)
self->mSlowScriptCheckpoint = TimeStamp::NowLoRes();
if (response == nsGlobalWindow::AlwaysContinueSlowScript)
Preferences::SetInt(prefName, 0);
return true;
}
void
XPCJSContext::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(NS_LITERAL_STRING("due-to-JS-OOM"),
/* anonymize = */ false,
/* minimizeMemoryUsage = */ false);
}
void
XPCJSContext::CustomLargeAllocationFailureCallback()
{
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
if (os) {
os->NotifyObservers(nullptr, "memory-pressure", u"heap-minimize");
}
}
size_t
XPCJSContext::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 += CycleCollectedJSContext::SizeOfExcludingThis(mallocSizeOf);
// There are other XPCJSContext 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;
}
/***************************************************************************/
#define JS_OPTIONS_DOT_STR "javascript.options."
static void
ReloadPrefsCallback(const char* pref, void* data)
{
XPCJSContext* xpccx = reinterpret_cast<XPCJSContext*>(data);
JSContext* cx = xpccx->Context();
bool safeMode = false;
nsCOMPtr<nsIXULRuntime> xr = do_GetService("@mozilla.org/xre/runtime;1");
if (xr) {
xr->GetInSafeMode(&safeMode);
}
bool useBaseline = Preferences::GetBool(JS_OPTIONS_DOT_STR "baselinejit") && !safeMode;
bool useIon = Preferences::GetBool(JS_OPTIONS_DOT_STR "ion") && !safeMode;
bool useAsmJS = Preferences::GetBool(JS_OPTIONS_DOT_STR "asmjs") && !safeMode;
bool useWasm = Preferences::GetBool(JS_OPTIONS_DOT_STR "wasm") && !safeMode;
bool useWasmBaseline = Preferences::GetBool(JS_OPTIONS_DOT_STR "wasm_baselinejit") && !safeMode;
bool throwOnAsmJSValidationFailure = Preferences::GetBool(JS_OPTIONS_DOT_STR
"throw_on_asmjs_validation_failure");
bool useNativeRegExp = Preferences::GetBool(JS_OPTIONS_DOT_STR "native_regexp") && !safeMode;
bool parallelParsing = Preferences::GetBool(JS_OPTIONS_DOT_STR "parallel_parsing");
bool offthreadIonCompilation = Preferences::GetBool(JS_OPTIONS_DOT_STR
"ion.offthread_compilation");
bool useBaselineEager = Preferences::GetBool(JS_OPTIONS_DOT_STR
"baselinejit.unsafe_eager_compilation");
bool useIonEager = Preferences::GetBool(JS_OPTIONS_DOT_STR "ion.unsafe_eager_compilation");
sDiscardSystemSource = Preferences::GetBool(JS_OPTIONS_DOT_STR "discardSystemSource");
bool useAsyncStack = Preferences::GetBool(JS_OPTIONS_DOT_STR "asyncstack");
bool throwOnDebuggeeWouldRun = Preferences::GetBool(JS_OPTIONS_DOT_STR
"throw_on_debuggee_would_run");
bool dumpStackOnDebuggeeWouldRun = Preferences::GetBool(JS_OPTIONS_DOT_STR
"dump_stack_on_debuggee_would_run");
bool werror = Preferences::GetBool(JS_OPTIONS_DOT_STR "werror");
bool extraWarnings = Preferences::GetBool(JS_OPTIONS_DOT_STR "strict");
sSharedMemoryEnabled = Preferences::GetBool(JS_OPTIONS_DOT_STR "shared_memory");
#ifdef DEBUG
sExtraWarningsForSystemJS = Preferences::GetBool(JS_OPTIONS_DOT_STR "strict.debug");
#endif
bool arrayProtoValues = Preferences::GetBool(JS_OPTIONS_DOT_STR "array_prototype_values");
JS::ContextOptionsRef(cx).setBaseline(useBaseline)
.setIon(useIon)
.setAsmJS(useAsmJS)
.setWasm(useWasm)
.setWasmAlwaysBaseline(useWasmBaseline)
.setThrowOnAsmJSValidationFailure(throwOnAsmJSValidationFailure)
.setNativeRegExp(useNativeRegExp)
.setAsyncStack(useAsyncStack)
.setThrowOnDebuggeeWouldRun(throwOnDebuggeeWouldRun)
.setDumpStackOnDebuggeeWouldRun(dumpStackOnDebuggeeWouldRun)
.setWerror(werror)
.setExtraWarnings(extraWarnings)
.setArrayProtoValues(arrayProtoValues);
JS_SetParallelParsingEnabled(cx, parallelParsing);
JS_SetOffthreadIonCompilationEnabled(cx, offthreadIonCompilation);
JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_BASELINE_WARMUP_TRIGGER,
useBaselineEager ? 0 : -1);
JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_ION_WARMUP_TRIGGER,
useIonEager ? 0 : -1);
}
XPCJSContext::~XPCJSContext()
{
// Elsewhere we abort immediately if XPCJSContext initialization fails.
// Therefore the context must be non-null.
MOZ_ASSERT(MaybeContext());
// 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. Null out the relevant callbacks.
js::SetActivityCallback(Context(), nullptr, nullptr);
JS_RemoveFinalizeCallback(Context(), FinalizeCallback);
JS_RemoveWeakPointerZoneGroupCallback(Context(), WeakPointerZoneGroupCallback);
JS_RemoveWeakPointerCompartmentCallback(Context(), WeakPointerCompartmentCallback);
// Clear any pending exception. It might be an XPCWrappedJS, and if we try
// to destroy it later we will crash.
SetPendingException(nullptr);
JS::SetGCSliceCallback(Context(), mPrevGCSliceCallback);
xpc_DelocalizeContext(Context());
if (mWatchdogManager->GetWatchdog())
mWatchdogManager->StopWatchdog();
if (mCallContext)
mCallContext->SystemIsBeingShutDown();
auto rtPrivate = static_cast<PerThreadAtomCache*>(JS_GetContextPrivate(Context()));
delete rtPrivate;
JS_SetContextPrivate(Context(), nullptr);
// clean up and destroy maps...
mWrappedJSMap->ShutdownMarker();
delete mWrappedJSMap;
mWrappedJSMap = nullptr;
delete mWrappedJSClassMap;
mWrappedJSClassMap = nullptr;
delete mIID2NativeInterfaceMap;
mIID2NativeInterfaceMap = nullptr;
delete mClassInfo2NativeSetMap;
mClassInfo2NativeSetMap = nullptr;
delete mNativeSetMap;
mNativeSetMap = nullptr;
delete mThisTranslatorMap;
mThisTranslatorMap = nullptr;
delete mDyingWrappedNativeProtoMap;
mDyingWrappedNativeProtoMap = nullptr;
#ifdef MOZ_ENABLE_PROFILER_SPS
// Tell the profiler that the context is gone
if (PseudoStack* stack = mozilla_get_pseudo_stack())
stack->sampleContext(nullptr);
#endif
Preferences::UnregisterCallback(ReloadPrefsCallback, JS_OPTIONS_DOT_STR, this);
}
// If |*anonymizeID| is non-zero and this is a user compartment, the name will
// be anonymized.
static void
GetCompartmentName(JSCompartment* c, nsCString& name, int* anonymizeID,
bool replaceSlashes)
{
if (js::IsAtomsCompartment(c)) {
name.AssignLiteral("atoms");
} else if (*anonymizeID && !js::IsSystemCompartment(c)) {
name.AppendPrintf("<anonymized-%d>", *anonymizeID);
*anonymizeID += 1;
} else if (JSPrincipals* principals = JS_GetCompartmentPrincipals(c)) {
nsresult rv = nsJSPrincipals::get(principals)->GetScriptLocation(name);
if (NS_FAILED(rv)) {
name.AssignLiteral("(unknown)");
}
// If the compartment's location (name) differs from the principal's
// script location, append the compartment's location to allow
// differentiation of multiple compartments owned by the same principal
// (e.g. components owned by the system or null principal).
CompartmentPrivate* compartmentPrivate = CompartmentPrivate::Get(c);
if (compartmentPrivate) {
const nsACString& location = compartmentPrivate->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.ReplaceASCII(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:
// inProcessTabChildGlobal?ownedBy=http://www.example.com/
// The owner should be omitted if it's not a chrome: URI and we're
// anonymizing.
static const char* ownedByPrefix =
"inProcessTabChildGlobal?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::GetCurrentCompartmentName(JSContext* cx, nsCString& name)
{
RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
if (!global) {
name.AssignLiteral("no global");
return;
}
JSCompartment* compartment = GetObjectCompartment(global);
int anonymizeID = 0;
GetCompartmentName(compartment, name, &anonymizeID, false);
}
void
xpc::AddGCCallback(xpcGCCallback cb)
{
XPCJSContext::Get()->AddGCCallback(cb);
}
void
xpc::RemoveGCCallback(xpcGCCallback cb)
{
XPCJSContext::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 = nsXPConnect::GetContextInstance()->Context();
return JS::UserCompartmentCount(cx);
}
class JSMainRuntimeTemporaryPeakReporter final : public nsIMemoryReporter
{
~JSMainRuntimeTemporaryPeakReporter() {}
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 ZCREPORT* macros are for
// compartments and zones; they aggregate any entries smaller than
// SUNDRIES_THRESHOLD into the "sundries/gc-heap" and "sundries/malloc-heap"
// entries for the compartment.
#define SUNDRIES_THRESHOLD js::MemoryReportingSundriesThreshold()
#define REPORT(_path, _kind, _units, _amount, _desc) \
handleReport->Callback(EmptyCString(), _path, \
nsIMemoryReporter::_kind, \
nsIMemoryReporter::_units, _amount, \
NS_LITERAL_CSTRING(_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(EmptyCString(), _path, \
nsIMemoryReporter::KIND_NONHEAP, \
nsIMemoryReporter::UNITS_BYTES, amount, \
NS_LITERAL_CSTRING(_desc), data); \
gcTotal += amount; \
} while (0)
// Report compartment/zone non-GC (KIND_HEAP) bytes.
#define ZCREPORT_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(EmptyCString(), _path, \
nsIMemoryReporter::KIND_HEAP, \
nsIMemoryReporter::UNITS_BYTES, amount, \
NS_LITERAL_CSTRING(_desc), data); \
} else { \
sundriesMallocHeap += amount; \
} \
} while (0)
// Report compartment/zone GC bytes.
#define ZCREPORT_GC_BYTES(_path, _amount, _desc) \
do { \
size_t amount = _amount; /* evaluate _amount only once */ \
if (amount >= SUNDRIES_THRESHOLD) { \
handleReport->Callback(EmptyCString(), _path, \
nsIMemoryReporter::KIND_NONHEAP, \
nsIMemoryReporter::UNITS_BYTES, amount, \
NS_LITERAL_CSTRING(_desc), data); \
gcTotal += amount; \
} else { \
sundriesGCHeap += amount; \
} \
} while (0)
// Report runtime bytes.
#define RREPORT_BYTES(_path, _kind, _amount, _desc) \
do { \
size_t amount = _amount; /* evaluate _amount only once */ \
handleReport->Callback(EmptyCString(), _path, \
nsIMemoryReporter::_kind, \
nsIMemoryReporter::UNITS_BYTES, amount, \
NS_LITERAL_CSTRING(_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(EmptyCString(), _path, \
nsIMemoryReporter::_kind, \
nsIMemoryReporter::UNITS_BYTES, amount, \
NS_LITERAL_CSTRING(_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, sundriesGCHeap = 0, sundriesMallocHeap = 0;
MOZ_ASSERT(!gcTotalOut == zStats.isTotals);
ZCREPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("symbols/gc-heap"),
zStats.symbolsGCHeap,
"Symbols.");
ZCREPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("gc-heap-arena-admin"),
zStats.gcHeapArenaAdmin,
"Bookkeeping information and alignment padding within GC arenas.");
ZCREPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("unused-gc-things"),
zStats.unusedGCThings.totalSize(),
"Unused GC thing cells within non-empty arenas.");
ZCREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("unique-id-map"),
zStats.uniqueIdMap,
"Address-independent cell identities.");
ZCREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("shape-tables"),
zStats.shapeTables,
"Tables storing shape information.");
ZCREPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("lazy-scripts/gc-heap"),
zStats.lazyScriptsGCHeap,
"Scripts that haven't executed yet.");
ZCREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("lazy-scripts/malloc-heap"),
zStats.lazyScriptsMallocHeap,
"Lazy script tables containing closed-over bindings or inner functions.");
ZCREPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("jit-codes-gc-heap"),
zStats.jitCodesGCHeap,
"References to executable code pools used by the JITs.");
ZCREPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("object-groups/gc-heap"),
zStats.objectGroupsGCHeap,
"Classification and type inference information about objects.");
ZCREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("object-groups/malloc-heap"),
zStats.objectGroupsMallocHeap,
"Object group addenda.");
ZCREPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("scopes/gc-heap"),
zStats.scopesGCHeap,
"Scope information for scripts.");
ZCREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("scopes/malloc-heap"),
zStats.scopesMallocHeap,
"Arrays of binding names and other binding-related data.");
ZCREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("type-pool"),
zStats.typePool,
"Type sets and related data.");
ZCREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("baseline/optimized-stubs"),
zStats.baselineStubsOptimized,
"The Baseline JIT's optimized IC stubs (excluding code).");
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);
// 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(NS_LITERAL_CSTRING(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 "%d, copies=%d, \"%s\"%s)/",
info.length, info.numCopies, escapedString.get(),
truncated ? " (truncated)" : "");
if (info.gcHeapLatin1 > 0) {
REPORT_GC_BYTES(path + NS_LITERAL_CSTRING("gc-heap/latin1"),
info.gcHeapLatin1,
"Latin1 strings. " MAYBE_INLINE);
}
if (info.gcHeapTwoByte > 0) {
REPORT_GC_BYTES(path + NS_LITERAL_CSTRING("gc-heap/two-byte"),
info.gcHeapTwoByte,
"TwoByte strings. " MAYBE_INLINE);
}
if (info.mallocHeapLatin1 > 0) {
REPORT_BYTES(path + NS_LITERAL_CSTRING("malloc-heap/latin1"),
KIND_HEAP, info.mallocHeapLatin1,
"Non-inline Latin1 string characters. " MAYBE_OVERALLOCATED);
}
if (info.mallocHeapTwoByte > 0) {
REPORT_BYTES(path + NS_LITERAL_CSTRING("malloc-heap/two-byte"),
KIND_HEAP, info.mallocHeapTwoByte,
"Non-inline TwoByte string characters. " MAYBE_OVERALLOCATED);
}
}
nsCString nonNotablePath = pathPrefix;
nonNotablePath += (zStats.isTotals || anonymize)
? NS_LITERAL_CSTRING("strings/")
: NS_LITERAL_CSTRING("strings/string(<non-notable strings>)/");
if (zStats.stringInfo.gcHeapLatin1 > 0) {
REPORT_GC_BYTES(nonNotablePath + NS_LITERAL_CSTRING("gc-heap/latin1"),
zStats.stringInfo.gcHeapLatin1,
"Latin1 strings. " MAYBE_INLINE);
}
if (zStats.stringInfo.gcHeapTwoByte > 0) {
REPORT_GC_BYTES(nonNotablePath + NS_LITERAL_CSTRING("gc-heap/two-byte"),
zStats.stringInfo.gcHeapTwoByte,
"TwoByte strings. " MAYBE_INLINE);
}
if (zStats.stringInfo.mallocHeapLatin1 > 0) {
REPORT_BYTES(nonNotablePath + NS_LITERAL_CSTRING("malloc-heap/latin1"),
KIND_HEAP, zStats.stringInfo.mallocHeapLatin1,
"Non-inline Latin1 string characters. " MAYBE_OVERALLOCATED);
}
if (zStats.stringInfo.mallocHeapTwoByte > 0) {
REPORT_BYTES(nonNotablePath + NS_LITERAL_CSTRING("malloc-heap/two-byte"),
KIND_HEAP, zStats.stringInfo.mallocHeapTwoByte,
"Non-inline TwoByte string characters. " MAYBE_OVERALLOCATED);
}
if (stringsNotableAboutMemoryGCHeap > 0) {
MOZ_ASSERT(!zStats.isTotals);
REPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("strings/string(<about-memory>)/gc-heap"),
stringsNotableAboutMemoryGCHeap,
"Strings that contain the characters '" STRING_LENGTH "', which "
"are probably from about:memory itself." MAYBE_INLINE
" We filter them out rather than display them, because displaying "
"them would create even more such strings every time about:memory "
"is refreshed.");
}
if (stringsNotableAboutMemoryMallocHeap > 0) {
MOZ_ASSERT(!zStats.isTotals);
REPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("strings/string(<about-memory>)/malloc-heap"),
KIND_HEAP, stringsNotableAboutMemoryMallocHeap,
"Non-inline string characters of strings that contain the "
"characters '" STRING_LENGTH "', which are probably from "
"about:memory itself. " MAYBE_OVERALLOCATED
" We filter them out rather than display them, because displaying "
"them would create even more such strings every time about:memory "
"is refreshed.");
}
const JS::ShapeInfo& shapeInfo = zStats.shapeInfo;
if (shapeInfo.shapesGCHeapTree > 0) {
REPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("shapes/gc-heap/tree"),
shapeInfo.shapesGCHeapTree,
"Shapes in a property tree.");
}
if (shapeInfo.shapesGCHeapDict > 0) {
REPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("shapes/gc-heap/dict"),
shapeInfo.shapesGCHeapDict,
"Shapes in dictionary mode.");
}
if (shapeInfo.shapesGCHeapBase > 0) {
REPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("shapes/gc-heap/base"),
shapeInfo.shapesGCHeapBase,
"Base shapes, which collate data common to many shapes.");
}
if (shapeInfo.shapesMallocHeapTreeTables > 0) {
REPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("shapes/malloc-heap/tree-tables"),
KIND_HEAP, shapeInfo.shapesMallocHeapTreeTables,
"Property tables of shapes in a property tree.");
}
if (shapeInfo.shapesMallocHeapDictTables > 0) {
REPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("shapes/malloc-heap/dict-tables"),
KIND_HEAP, shapeInfo.shapesMallocHeapDictTables,
"Property tables of shapes in dictionary mode.");
}
if (shapeInfo.shapesMallocHeapTreeKids > 0) {
REPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("shapes/malloc-heap/tree-kids"),
KIND_HEAP, shapeInfo.shapesMallocHeapTreeKids,
"Kid hashes of shapes in a property tree.");
}
if (sundriesGCHeap > 0) {
// We deliberately don't use ZCREPORT_GC_BYTES here.
REPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("sundries/gc-heap"),
sundriesGCHeap,
"The sum of all 'gc-heap' measurements that are too small to be "
"worth showing individually.");
}
if (sundriesMallocHeap > 0) {
// We deliberately don't use ZCREPORT_BYTES here.
REPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("sundries/malloc-heap"),
KIND_HEAP, sundriesMallocHeap,
"The sum of all 'malloc-heap' measurements that are too small to "
"be worth showing individually.");
}
if (gcTotalOut)
*gcTotalOut += gcTotal;
# undef STRING_LENGTH
}
static void
ReportClassStats(const ClassInfo& classInfo, const nsACString& path,
nsIHandleReportCallback* handleReport,
nsISupports* data, size_t& gcTotal)
{
// We deliberately don't use ZCREPORT_BYTES, so that these per-class values
// don't go into sundries.
if (classInfo.objectsGCHeap > 0) {
REPORT_GC_BYTES(path + NS_LITERAL_CSTRING("objects/gc-heap"),
classInfo.objectsGCHeap,
"Objects, including fixed slots.");
}
if (classInfo.objectsMallocHeapSlots > 0) {
REPORT_BYTES(path + NS_LITERAL_CSTRING("objects/malloc-heap/slots"),
KIND_HEAP, classInfo.objectsMallocHeapSlots,
"Non-fixed object slots.");
}
if (classInfo.objectsMallocHeapElementsNormal > 0) {
REPORT_BYTES(path + NS_LITERAL_CSTRING("objects/malloc-heap/elements/normal"),
KIND_HEAP, classInfo.objectsMallocHeapElementsNormal,
"Normal (non-wasm) indexed elements.");
}
if (classInfo.objectsMallocHeapElementsAsmJS > 0) {
REPORT_BYTES(path + NS_LITERAL_CSTRING("objects/malloc-heap/elements/asm.js"),
KIND_HEAP, classInfo.objectsMallocHeapElementsAsmJS,
"asm.js array buffer elements allocated in the malloc heap.");
}
if (classInfo.objectsMallocHeapMisc > 0) {
REPORT_BYTES(path + NS_LITERAL_CSTRING("objects/malloc-heap/misc"),
KIND_HEAP, classInfo.objectsMallocHeapMisc,
"Miscellaneous object data.");
}
if (classInfo.objectsNonHeapElementsNormal > 0) {
REPORT_BYTES(path + NS_LITERAL_CSTRING("objects/non-heap/elements/normal"),
KIND_NONHEAP, classInfo.objectsNonHeapElementsNormal,
"Memory-mapped non-shared array buffer elements.");
}
if (classInfo.objectsNonHeapElementsShared > 0) {
REPORT_BYTES(path + NS_LITERAL_CSTRING("objects/non-heap/elements/shared"),
KIND_NONHEAP, classInfo.objectsNonHeapElementsShared,
"Memory-mapped shared array buffer elements. These elements are "
"shared between one or more runtimes; the reported size is divided "
"by the buffer's refcount.");
}
// WebAssembly memories are always non-heap-allocated (mmap). We never put
// these under sundries, because (a) in practice they're almost always
// larger than the sundries threshold, and (b) we'd need a third category of
// sundries ("non-heap"), which would be a pain.
if (classInfo.objectsNonHeapElementsWasm > 0) {
REPORT_BYTES(path + NS_LITERAL_CSTRING("objects/non-heap/elements/wasm"),
KIND_NONHEAP, classInfo.objectsNonHeapElementsWasm,
"wasm/asm.js array buffer elements allocated outside both the "
"malloc heap and the GC heap.");
}
if (classInfo.objectsNonHeapCodeWasm > 0) {
REPORT_BYTES(path + NS_LITERAL_CSTRING("objects/non-heap/code/wasm"),
KIND_NONHEAP, classInfo.objectsNonHeapCodeWasm,
"AOT-compiled wasm/asm.js code.");
}
// Although wasm guard pages aren't committed in memory they can be very
// large and contribute greatly to vsize and so are worth reporting.
if (classInfo.wasmGuardPages > 0) {
REPORT_BYTES(NS_LITERAL_CSTRING("wasm-guard-pages"),
KIND_OTHER, classInfo.wasmGuardPages,
"Guard pages mapped after the end of wasm memories, reserved for "
"optimization tricks, but not committed and thus never contributing"
" to RSS, only vsize.");
}
}
static void
ReportCompartmentStats(const JS::CompartmentStats& cStats,
const xpc::CompartmentStatsExtras& extras,
amIAddonManager* addonManager,
nsIHandleReportCallback* handleReport,
nsISupports* data, size_t* gcTotalOut = nullptr)
{
static const nsDependentCString addonPrefix("explicit/add-ons/");
size_t gcTotal = 0, sundriesGCHeap = 0, sundriesMallocHeap = 0;
nsAutoCString cJSPathPrefix(extras.jsPathPrefix);
nsAutoCString cDOMPathPrefix(extras.domPathPrefix);
MOZ_ASSERT(!gcTotalOut == cStats.isTotals);
// Only attempt to prefix if we got a location and the path wasn't already
// prefixed.
if (extras.location && addonManager &&
cJSPathPrefix.Find(addonPrefix, false, 0, 0) != 0) {
nsAutoCString addonId;
bool ok;
if (NS_SUCCEEDED(addonManager->MapURIToAddonID(extras.location,
addonId, &ok))
&& ok) {
// Insert the add-on id as "add-ons/@id@/" after "explicit/" to
// aggregate add-on compartments.
static const size_t explicitLength = strlen("explicit/");
addonId.Insert(NS_LITERAL_CSTRING("add-ons/"), 0);
addonId += "/";
cJSPathPrefix.Insert(addonId, explicitLength);
cDOMPathPrefix.Insert(addonId, explicitLength);
}
}
nsCString nonNotablePath = cJSPathPrefix;
nonNotablePath += cStats.isTotals
? NS_LITERAL_CSTRING("classes/")
: NS_LITERAL_CSTRING("classes/class(<non-notable classes>)/");
ReportClassStats(cStats.classInfo, nonNotablePath, handleReport, data,
gcTotal);
for (size_t i = 0; i < cStats.notableClasses.length(); i++) {
MOZ_ASSERT(!cStats.isTotals);
const JS::NotableClassInfo& classInfo = cStats.notableClasses[i];
nsCString classPath = cJSPathPrefix +
nsPrintfCString("classes/class(%s)/", classInfo.className_);
ReportClassStats(classInfo, classPath, handleReport, data, gcTotal);
}
// Note that we use cDOMPathPrefix here. This is because we measure orphan
// DOM nodes in the JS reporter, but we want to report them in a "dom"
// sub-tree rather than a "js" sub-tree.
ZCREPORT_BYTES(cDOMPathPrefix + NS_LITERAL_CSTRING("orphan-nodes"),
cStats.objectsPrivate,
"Orphan DOM nodes, i.e. those that are only reachable from JavaScript "
"objects.");
ZCREPORT_GC_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("scripts/gc-heap"),
cStats.scriptsGCHeap,
"JSScript instances. There is one per user-defined function in a "
"script, and one for the top-level code in a script.");
ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("scripts/malloc-heap/data"),
cStats.scriptsMallocHeapData,
"Various variable-length tables in JSScripts.");
ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("baseline/data"),
cStats.baselineData,
"The Baseline JIT's compilation data (BaselineScripts).");
ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("baseline/fallback-stubs"),
cStats.baselineStubsFallback,
"The Baseline JIT's fallback IC stubs (excluding code).");
ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("ion-data"),
cStats.ionData,
"The IonMonkey JIT's compilation data (IonScripts).");
ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("type-inference/type-scripts"),
cStats.typeInferenceTypeScripts,
"Type sets associated with scripts.");
ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("type-inference/allocation-site-tables"),
cStats.typeInferenceAllocationSiteTables,
"Tables of type objects associated with allocation sites.");
ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("type-inference/array-type-tables"),
cStats.typeInferenceArrayTypeTables,
"Tables of type objects associated with array literals.");
ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("type-inference/object-type-tables"),
cStats.typeInferenceObjectTypeTables,
"Tables of type objects associated with object literals.");
ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("compartment-object"),
cStats.compartmentObject,
"The JSCompartment object itself.");
ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("compartment-tables"),
cStats.compartmentTables,
"Compartment-wide tables storing object group information and wasm instances.");
ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("inner-views"),
cStats.innerViewsTable,
"The table for array buffer inner views.");
ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("lazy-array-buffers"),
cStats.lazyArrayBuffersTable,
"The table for typed object lazy array buffers.");
ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("object-metadata"),
cStats.objectMetadataTable,
"The table used by debugging tools for tracking object metadata");
ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("cross-compartment-wrapper-table"),
cStats.crossCompartmentWrappersTable,
"The cross-compartment wrapper table.");
ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("regexp-compartment"),
cStats.regexpCompartment,
"The regexp compartment and regexp data.");
ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("saved-stacks-set"),
cStats.savedStacksSet,
"The saved stacks set.");
ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("non-syntactic-lexical-scopes-table"),
cStats.nonSyntacticLexicalScopesTable,
"The non-syntactic lexical scopes table.");
ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("jit-compartment"),
cStats.jitCompartment,
"The JIT compartment.");
ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("private-data"),
cStats.privateData,
"Extra data attached to the compartment by XPConnect, including "
"its wrapped-js.");
if (sundriesGCHeap > 0) {
// We deliberately don't use ZCREPORT_GC_BYTES here.
REPORT_GC_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("sundries/gc-heap"),
sundriesGCHeap,
"The sum of all 'gc-heap' measurements that are too small to be "
"worth showing individually.");
}
if (sundriesMallocHeap > 0) {
// We deliberately don't use ZCREPORT_BYTES here.
REPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("sundries/malloc-heap"),
KIND_HEAP, sundriesMallocHeap,
"The sum of all 'malloc-heap' measurements that are too small to "
"be worth showing individually.");
}
if (gcTotalOut)
*gcTotalOut += gcTotal;
}
static void
ReportScriptSourceStats(const ScriptSourceInfo& scriptSourceInfo,
const nsACString& path,
nsIHandleReportCallback* handleReport,
nsISupports* data, size_t& rtTotal)
{
if (scriptSourceInfo.misc > 0) {
RREPORT_BYTES(path + NS_LITERAL_CSTRING("misc"),
KIND_HEAP, scriptSourceInfo.misc,
"Miscellaneous data relating to JavaScript source code.");
}
}
static void
ReportJSRuntimeExplicitTreeStats(const JS::RuntimeStats& rtStats,
const nsACString& rtPath,
amIAddonManager* addonManager,
nsIHandleReportCallback* handleReport,
nsISupports* data,
bool anonymize,
size_t* rtTotalOut)
{
size_t gcTotal = 0;
for (size_t i = 0; i < rtStats.zoneStatsVector.length(); i++) {
const JS::ZoneStats& zStats = rtStats.zoneStatsVector[i];
const xpc::ZoneStatsExtras* extras =
static_cast<const xpc::ZoneStatsExtras*>(zStats.extra);
ReportZoneStats(zStats, *extras, handleReport, data, anonymize,
&gcTotal);
}
for (size_t i = 0; i < rtStats.compartmentStatsVector.length(); i++) {
const JS::CompartmentStats& cStats = rtStats.compartmentStatsVector[i];
const xpc::CompartmentStatsExtras* extras =
static_cast<const xpc::CompartmentStatsExtras*>(cStats.extra);
ReportCompartmentStats(cStats, *extras, addonManager, handleReport,
data, &gcTotal);
}
// Report the rtStats.runtime numbers under "runtime/", and compute their
// total for later.
size_t rtTotal = 0;
RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/runtime-object"),
KIND_HEAP, rtStats.runtime.object,
"The JSRuntime object.");
RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/atoms-table"),
KIND_HEAP, rtStats.runtime.atomsTable,
"The atoms table.");
RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/contexts"),
KIND_HEAP, rtStats.runtime.contexts,
"JSContext objects and structures that belong to them.");
RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/temporary"),
KIND_HEAP, rtStats.runtime.temporary,
"Transient data (mostly parse nodes) held by the JSRuntime during "
"compilation.");
RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/interpreter-stack"),
KIND_HEAP, rtStats.runtime.interpreterStack,
"JS interpreter frames.");
RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/math-cache"),
KIND_HEAP, rtStats.runtime.mathCache,
"The math cache.");
RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/shared-immutable-strings-cache"),
KIND_HEAP, rtStats.runtime.sharedImmutableStringsCache,
"Immutable strings (such as JS scripts' source text) shared across all JSRuntimes.");
RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/shared-intl-data"),
KIND_HEAP, rtStats.runtime.sharedIntlData,
"Shared internationalization data.");
RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/uncompressed-source-cache"),
KIND_HEAP, rtStats.runtime.uncompressedSourceCache,
"The uncompressed source code cache.");
RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/script-data"),
KIND_HEAP, rtStats.runtime.scriptData,
"The table holding script data shared in the runtime.");
nsCString nonNotablePath =
rtPath + nsPrintfCString("runtime/script-sources/source(scripts=%d, <non-notable files>)/",
rtStats.runtime.scriptSourceInfo.numScripts);
ReportScriptSourceStats(rtStats.runtime.scriptSourceInfo,
nonNotablePath, handleReport, data, rtTotal);
for (size_t i = 0; i < rtStats.runtime.notableScriptSources.length(); i++) {
const JS::NotableScriptSourceInfo& scriptSourceInfo =
rtStats.runtime.notableScriptSources[i];
// Escape / to \ before we put the filename into the memory reporter
// path, because we don't want any forward slashes in the string to
// count as path separators. Consumers of memory reporters (e.g.
// about:memory) will convert them back to / after doing path
// splitting.
nsCString escapedFilename;
if (anonymize) {
escapedFilename.AppendPrintf("<anonymized-source-%d>", int(i));
} else {
nsDependentCString filename(scriptSourceInfo.filename_);
escapedFilename.Append(filename);
escapedFilename.ReplaceSubstring("/", "\\");
}
nsCString notablePath = rtPath +
nsPrintfCString("runtime/script-sources/source(scripts=%d, %s)/",
scriptSourceInfo.numScripts, escapedFilename.get());
ReportScriptSourceStats(scriptSourceInfo, notablePath,
handleReport, data, rtTotal);
}
RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/code/ion"),
KIND_NONHEAP, rtStats.runtime.code.ion,
"Code generated by the IonMonkey JIT.");
RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/code/baseline"),
KIND_NONHEAP, rtStats.runtime.code.baseline,
"Code generated by the Baseline JIT.");
RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/code/regexp"),
KIND_NONHEAP, rtStats.runtime.code.regexp,
"Code generated by the regexp JIT.");
RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/code/other"),
KIND_NONHEAP, rtStats.runtime.code.other,
"Code generated by the JITs for wrappers and trampolines.");
RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/code/unused"),
KIND_NONHEAP, rtStats.runtime.code.unused,
"Memory allocated by one of the JITs to hold code, but which is "
"currently unused.");
RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/gc/marker"),
KIND_HEAP, rtStats.runtime.gc.marker,
"The GC mark stack and gray roots.");
RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/gc/nursery-committed"),
KIND_NONHEAP, rtStats.runtime.gc.nurseryCommitted,
"Memory being used by the GC's nursery.");
RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/gc/nursery-malloced-buffers"),
KIND_HEAP, rtStats.runtime.gc.nurseryMallocedBuffers,
"Out-of-line slots and elements belonging to objects in the nursery.");
RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/gc/store-buffer/vals"),
KIND_HEAP, rtStats.runtime.gc.storeBufferVals,
"Values in the store buffer.");
RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/gc/store-buffer/cells"),
KIND_HEAP, rtStats.runtime.gc.storeBufferCells,
"Cells in the store buffer.");
RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/gc/store-buffer/slots"),
KIND_HEAP, rtStats.runtime.gc.storeBufferSlots,
"Slots in the store buffer.");
RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/gc/store-buffer/whole-cells"),
KIND_HEAP, rtStats.runtime.gc.storeBufferWholeCells,
"Whole cells in the store buffer.");
RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/gc/store-buffer/generics"),