michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ michael@0: /* vim: set ts=8 sts=4 et sw=4 tw=99: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* Per JSRuntime object */ michael@0: michael@0: #include "mozilla/MemoryReporting.h" michael@0: michael@0: #include "xpcprivate.h" michael@0: #include "xpcpublic.h" michael@0: #include "XPCWrapper.h" michael@0: #include "XPCJSMemoryReporter.h" michael@0: #include "WrapperFactory.h" michael@0: #include "dom_quickstubs.h" michael@0: #include "mozJSComponentLoader.h" michael@0: michael@0: #include "nsIMemoryInfoDumper.h" michael@0: #include "nsIMemoryReporter.h" michael@0: #include "nsIObserverService.h" michael@0: #include "nsIDebug2.h" michael@0: #include "amIAddonManager.h" michael@0: #include "nsPIDOMWindow.h" michael@0: #include "nsPrintfCString.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "mozilla/Telemetry.h" michael@0: #include "mozilla/Services.h" michael@0: michael@0: #include "nsContentUtils.h" michael@0: #include "nsCxPusher.h" michael@0: #include "nsCCUncollectableMarker.h" michael@0: #include "nsCycleCollectionNoteRootCallback.h" michael@0: #include "nsScriptLoader.h" michael@0: #include "jsfriendapi.h" michael@0: #include "jsprf.h" michael@0: #include "js/MemoryMetrics.h" michael@0: #include "js/OldDebugAPI.h" michael@0: #include "mozilla/dom/GeneratedAtomList.h" michael@0: #include "mozilla/dom/BindingUtils.h" michael@0: #include "mozilla/dom/Element.h" michael@0: #include "mozilla/dom/WindowBinding.h" michael@0: #include "mozilla/Atomics.h" michael@0: #include "mozilla/Attributes.h" michael@0: #include "AccessCheck.h" michael@0: #include "nsGlobalWindow.h" michael@0: #include "nsAboutProtocolUtils.h" michael@0: michael@0: #include "GeckoProfiler.h" michael@0: #include "nsIXULRuntime.h" michael@0: #include "nsJSPrincipals.h" michael@0: michael@0: #ifdef MOZ_CRASHREPORTER michael@0: #include "nsExceptionHandler.h" michael@0: #endif michael@0: michael@0: using namespace mozilla; michael@0: using namespace xpc; michael@0: using namespace JS; michael@0: using mozilla::dom::PerThreadAtomCache; michael@0: michael@0: /***************************************************************************/ michael@0: michael@0: const char* const XPCJSRuntime::mStrings[] = { michael@0: "constructor", // IDX_CONSTRUCTOR michael@0: "toString", // IDX_TO_STRING michael@0: "toSource", // IDX_TO_SOURCE michael@0: "lastResult", // IDX_LAST_RESULT michael@0: "returnCode", // IDX_RETURN_CODE michael@0: "value", // IDX_VALUE michael@0: "QueryInterface", // IDX_QUERY_INTERFACE michael@0: "Components", // IDX_COMPONENTS michael@0: "wrappedJSObject", // IDX_WRAPPED_JSOBJECT michael@0: "Object", // IDX_OBJECT michael@0: "Function", // IDX_FUNCTION michael@0: "prototype", // IDX_PROTOTYPE michael@0: "createInstance", // IDX_CREATE_INSTANCE michael@0: "item", // IDX_ITEM michael@0: "__proto__", // IDX_PROTO michael@0: "__iterator__", // IDX_ITERATOR michael@0: "__exposedProps__", // IDX_EXPOSEDPROPS michael@0: "eval", // IDX_EVAL michael@0: "controllers", // IDX_CONTROLLERS michael@0: }; michael@0: michael@0: /***************************************************************************/ michael@0: michael@0: static mozilla::Atomic sDiscardSystemSource(false); michael@0: michael@0: bool michael@0: xpc::ShouldDiscardSystemSource() { return sDiscardSystemSource; } michael@0: michael@0: static void * const UNMARK_ONLY = nullptr; michael@0: static void * const UNMARK_AND_SWEEP = (void *)1; michael@0: michael@0: static PLDHashOperator michael@0: NativeInterfaceSweeper(PLDHashTable *table, PLDHashEntryHdr *hdr, michael@0: uint32_t number, void *arg) michael@0: { michael@0: XPCNativeInterface* iface = ((IID2NativeInterfaceMap::Entry*)hdr)->value; michael@0: if (iface->IsMarked()) { michael@0: iface->Unmark(); michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: if (arg == UNMARK_ONLY) michael@0: return PL_DHASH_NEXT; michael@0: michael@0: XPCNativeInterface::DestroyInstance(iface); michael@0: return PL_DHASH_REMOVE; michael@0: } michael@0: michael@0: // *Some* NativeSets are referenced from mClassInfo2NativeSetMap. michael@0: // *All* NativeSets are referenced from mNativeSetMap. michael@0: // So, in mClassInfo2NativeSetMap we just clear references to the unmarked. michael@0: // In mNativeSetMap we clear the references to the unmarked *and* delete them. michael@0: michael@0: static PLDHashOperator michael@0: NativeUnMarkedSetRemover(PLDHashTable *table, PLDHashEntryHdr *hdr, michael@0: uint32_t number, void *arg) michael@0: { michael@0: XPCNativeSet* set = ((ClassInfo2NativeSetMap::Entry*)hdr)->value; michael@0: if (set->IsMarked()) michael@0: return PL_DHASH_NEXT; michael@0: return PL_DHASH_REMOVE; michael@0: } michael@0: michael@0: static PLDHashOperator michael@0: NativeSetSweeper(PLDHashTable *table, PLDHashEntryHdr *hdr, michael@0: uint32_t number, void *arg) michael@0: { michael@0: XPCNativeSet* set = ((NativeSetMap::Entry*)hdr)->key_value; michael@0: if (set->IsMarked()) { michael@0: set->Unmark(); michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: if (arg == UNMARK_ONLY) michael@0: return PL_DHASH_NEXT; michael@0: michael@0: XPCNativeSet::DestroyInstance(set); michael@0: return PL_DHASH_REMOVE; michael@0: } michael@0: michael@0: static PLDHashOperator michael@0: JSClassSweeper(PLDHashTable *table, PLDHashEntryHdr *hdr, michael@0: uint32_t number, void *arg) michael@0: { michael@0: XPCNativeScriptableShared* shared = michael@0: ((XPCNativeScriptableSharedMap::Entry*) hdr)->key; michael@0: if (shared->IsMarked()) { michael@0: shared->Unmark(); michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: if (arg == UNMARK_ONLY) michael@0: return PL_DHASH_NEXT; michael@0: michael@0: delete shared; michael@0: return PL_DHASH_REMOVE; michael@0: } michael@0: michael@0: static PLDHashOperator michael@0: DyingProtoKiller(PLDHashTable *table, PLDHashEntryHdr *hdr, michael@0: uint32_t number, void *arg) michael@0: { michael@0: XPCWrappedNativeProto* proto = michael@0: (XPCWrappedNativeProto*)((PLDHashEntryStub*)hdr)->key; michael@0: delete proto; michael@0: return PL_DHASH_REMOVE; michael@0: } michael@0: michael@0: static PLDHashOperator michael@0: DetachedWrappedNativeProtoMarker(PLDHashTable *table, PLDHashEntryHdr *hdr, michael@0: uint32_t number, void *arg) michael@0: { michael@0: XPCWrappedNativeProto* proto = michael@0: (XPCWrappedNativeProto*)((PLDHashEntryStub*)hdr)->key; michael@0: michael@0: proto->Mark(); michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: bool michael@0: XPCJSRuntime::CustomContextCallback(JSContext *cx, unsigned operation) michael@0: { michael@0: if (operation == JSCONTEXT_NEW) { michael@0: if (!OnJSContextNew(cx)) { michael@0: return false; michael@0: } michael@0: } else if (operation == JSCONTEXT_DESTROY) { michael@0: delete XPCContext::GetXPCContext(cx); michael@0: } michael@0: michael@0: nsTArray callbacks(extraContextCallbacks); michael@0: for (uint32_t i = 0; i < callbacks.Length(); ++i) { michael@0: if (!callbacks[i](cx, operation)) { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: class AsyncFreeSnowWhite : public nsRunnable michael@0: { michael@0: public: michael@0: NS_IMETHOD Run() michael@0: { michael@0: TimeStamp start = TimeStamp::Now(); michael@0: bool hadSnowWhiteObjects = nsCycleCollector_doDeferredDeletion(); michael@0: Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_ASYNC_SNOW_WHITE_FREEING, michael@0: uint32_t((TimeStamp::Now() - start).ToMilliseconds())); michael@0: if (hadSnowWhiteObjects && !mContinuation) { michael@0: mContinuation = true; michael@0: if (NS_FAILED(NS_DispatchToCurrentThread(this))) { michael@0: mActive = false; michael@0: } michael@0: } else { michael@0: mActive = false; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: void Dispatch(bool aContinuation = false) michael@0: { michael@0: if (mContinuation) { michael@0: mContinuation = aContinuation; michael@0: } michael@0: if (!mActive && NS_SUCCEEDED(NS_DispatchToCurrentThread(this))) { michael@0: mActive = true; michael@0: } michael@0: } michael@0: michael@0: AsyncFreeSnowWhite() : mContinuation(false), mActive(false) {} michael@0: michael@0: public: michael@0: bool mContinuation; michael@0: bool mActive; michael@0: }; michael@0: michael@0: namespace xpc { michael@0: michael@0: static uint32_t kLivingAdopters = 0; michael@0: michael@0: void michael@0: RecordAdoptedNode(JSCompartment *c) michael@0: { michael@0: CompartmentPrivate *priv = EnsureCompartmentPrivate(c); michael@0: if (!priv->adoptedNode) { michael@0: priv->adoptedNode = true; michael@0: ++kLivingAdopters; michael@0: } michael@0: } michael@0: michael@0: void michael@0: RecordDonatedNode(JSCompartment *c) michael@0: { michael@0: EnsureCompartmentPrivate(c)->donatedNode = true; michael@0: } michael@0: michael@0: CompartmentPrivate::~CompartmentPrivate() michael@0: { michael@0: MOZ_COUNT_DTOR(xpc::CompartmentPrivate); michael@0: michael@0: Telemetry::Accumulate(Telemetry::COMPARTMENT_ADOPTED_NODE, adoptedNode); michael@0: Telemetry::Accumulate(Telemetry::COMPARTMENT_DONATED_NODE, donatedNode); michael@0: if (adoptedNode) michael@0: --kLivingAdopters; michael@0: } michael@0: michael@0: static bool michael@0: TryParseLocationURICandidate(const nsACString& uristr, michael@0: CompartmentPrivate::LocationHint aLocationHint, michael@0: nsIURI** aURI) michael@0: { michael@0: static NS_NAMED_LITERAL_CSTRING(kGRE, "resource://gre/"); michael@0: static NS_NAMED_LITERAL_CSTRING(kToolkit, "chrome://global/"); michael@0: static NS_NAMED_LITERAL_CSTRING(kBrowser, "chrome://browser/"); michael@0: michael@0: if (aLocationHint == CompartmentPrivate::LocationHintAddon) { michael@0: // Blacklist some known locations which are clearly not add-on related. michael@0: if (StringBeginsWith(uristr, kGRE) || michael@0: StringBeginsWith(uristr, kToolkit) || michael@0: StringBeginsWith(uristr, kBrowser)) michael@0: return false; michael@0: } michael@0: michael@0: nsCOMPtr uri; michael@0: if (NS_FAILED(NS_NewURI(getter_AddRefs(uri), uristr))) michael@0: return false; michael@0: michael@0: nsAutoCString scheme; michael@0: if (NS_FAILED(uri->GetScheme(scheme))) michael@0: return false; michael@0: michael@0: // Cannot really map data: and blob:. michael@0: // Also, data: URIs are pretty memory hungry, which is kinda bad michael@0: // for memory reporter use. michael@0: if (scheme.EqualsLiteral("data") || scheme.EqualsLiteral("blob")) michael@0: return false; michael@0: michael@0: uri.forget(aURI); michael@0: return true; michael@0: } michael@0: michael@0: bool CompartmentPrivate::TryParseLocationURI(CompartmentPrivate::LocationHint aLocationHint, michael@0: nsIURI **aURI) michael@0: { michael@0: if (!aURI) michael@0: return false; michael@0: michael@0: // Need to parse the URI. michael@0: if (location.IsEmpty()) michael@0: return false; michael@0: michael@0: // Handle Sandbox location strings. michael@0: // A sandbox string looks like this: michael@0: // (from: :) michael@0: // where is user-provided via Cu.Sandbox() michael@0: // and and is the stack frame location michael@0: // from where Cu.Sandbox was called. michael@0: // furthermore is "free form", often using a michael@0: // "uri -> uri -> ..." chain. The following code will and must handle this michael@0: // common case. michael@0: // It should be noted that other parts of the code may already rely on the michael@0: // "format" of these strings, such as the add-on SDK. michael@0: michael@0: static const nsDependentCString from("(from: "); michael@0: static const nsDependentCString arrow(" -> "); michael@0: static const size_t fromLength = from.Length(); michael@0: static const size_t arrowLength = arrow.Length(); michael@0: michael@0: // See: XPCComponents.cpp#AssembleSandboxMemoryReporterName michael@0: int32_t idx = location.Find(from); michael@0: if (idx < 0) michael@0: return TryParseLocationURICandidate(location, aLocationHint, aURI); michael@0: michael@0: michael@0: // When parsing we're looking for the right-most URI. This URI may be in michael@0: // , so we try this first. michael@0: if (TryParseLocationURICandidate(Substring(location, 0, idx), aLocationHint, michael@0: aURI)) michael@0: return true; michael@0: michael@0: // Not in so we need to inspect and michael@0: // the chain that is potentially contained within and grab the rightmost michael@0: // item that is actually a URI. michael@0: michael@0: // First, hack off the :) part as well michael@0: int32_t ridx = location.RFind(NS_LITERAL_CSTRING(":")); michael@0: nsAutoCString chain(Substring(location, idx + fromLength, michael@0: ridx - idx - fromLength)); michael@0: michael@0: // Loop over the "->" chain. This loop also works for non-chains, or more michael@0: // correctly chains with only one item. michael@0: for (;;) { michael@0: idx = chain.RFind(arrow); michael@0: if (idx < 0) { michael@0: // This is the last chain item. Try to parse what is left. michael@0: return TryParseLocationURICandidate(chain, aLocationHint, aURI); michael@0: } michael@0: michael@0: // Try to parse current chain item michael@0: if (TryParseLocationURICandidate(Substring(chain, idx + arrowLength), michael@0: aLocationHint, aURI)) michael@0: return true; michael@0: michael@0: // Current chain item couldn't be parsed. michael@0: // Strip current item and continue. michael@0: chain = Substring(chain, 0, idx); michael@0: } michael@0: MOZ_ASSUME_UNREACHABLE("Chain parser loop does not terminate"); michael@0: } michael@0: michael@0: CompartmentPrivate* michael@0: EnsureCompartmentPrivate(JSObject *obj) michael@0: { michael@0: return EnsureCompartmentPrivate(js::GetObjectCompartment(obj)); michael@0: } michael@0: michael@0: CompartmentPrivate* michael@0: EnsureCompartmentPrivate(JSCompartment *c) michael@0: { michael@0: CompartmentPrivate *priv = GetCompartmentPrivate(c); michael@0: if (priv) michael@0: return priv; michael@0: priv = new CompartmentPrivate(c); michael@0: JS_SetCompartmentPrivate(c, priv); michael@0: return priv; michael@0: } michael@0: michael@0: XPCWrappedNativeScope* michael@0: MaybeGetObjectScope(JSObject *obj) michael@0: { michael@0: MOZ_ASSERT(obj); michael@0: JSCompartment *compartment = js::GetObjectCompartment(obj); michael@0: michael@0: MOZ_ASSERT(compartment); michael@0: CompartmentPrivate *priv = GetCompartmentPrivate(compartment); michael@0: if (!priv) michael@0: return nullptr; michael@0: michael@0: return priv->scope; michael@0: } michael@0: michael@0: static bool michael@0: PrincipalImmuneToScriptPolicy(nsIPrincipal* aPrincipal) michael@0: { michael@0: // System principal gets a free pass. michael@0: if (XPCWrapper::GetSecurityManager()->IsSystemPrincipal(aPrincipal)) michael@0: return true; michael@0: michael@0: // nsExpandedPrincipal gets a free pass. michael@0: nsCOMPtr ep = do_QueryInterface(aPrincipal); michael@0: if (ep) michael@0: return true; michael@0: michael@0: // Check whether our URI is an "about:" URI that allows scripts. If it is, michael@0: // we need to allow JS to run. michael@0: nsCOMPtr principalURI; michael@0: aPrincipal->GetURI(getter_AddRefs(principalURI)); michael@0: MOZ_ASSERT(principalURI); michael@0: bool isAbout; michael@0: nsresult rv = principalURI->SchemeIs("about", &isAbout); michael@0: if (NS_SUCCEEDED(rv) && isAbout) { michael@0: nsCOMPtr module; michael@0: rv = NS_GetAboutModule(principalURI, getter_AddRefs(module)); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: uint32_t flags; michael@0: rv = module->GetURIFlags(principalURI, &flags); michael@0: if (NS_SUCCEEDED(rv) && michael@0: (flags & nsIAboutModule::ALLOW_SCRIPT)) { michael@0: return true; michael@0: } michael@0: } michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: Scriptability::Scriptability(JSCompartment *c) : mScriptBlocks(0) michael@0: , mDocShellAllowsScript(true) michael@0: , mScriptBlockedByPolicy(false) michael@0: { michael@0: nsIPrincipal *prin = nsJSPrincipals::get(JS_GetCompartmentPrincipals(c)); michael@0: mImmuneToScriptPolicy = PrincipalImmuneToScriptPolicy(prin); michael@0: michael@0: // If we're not immune, we should have a real principal with a codebase URI. michael@0: // Check the URI against the new-style domain policy. michael@0: if (!mImmuneToScriptPolicy) { michael@0: nsIScriptSecurityManager* ssm = XPCWrapper::GetSecurityManager(); michael@0: nsCOMPtr codebase; michael@0: nsresult rv = prin->GetURI(getter_AddRefs(codebase)); michael@0: bool policyAllows; michael@0: if (NS_SUCCEEDED(rv) && codebase && michael@0: NS_SUCCEEDED(ssm->PolicyAllowsScript(codebase, &policyAllows))) michael@0: { michael@0: mScriptBlockedByPolicy = !policyAllows; michael@0: } else { michael@0: // Something went wrong - be safe and block script. michael@0: mScriptBlockedByPolicy = true; michael@0: } michael@0: } michael@0: } michael@0: michael@0: bool michael@0: Scriptability::Allowed() michael@0: { michael@0: return mDocShellAllowsScript && !mScriptBlockedByPolicy && michael@0: mScriptBlocks == 0; michael@0: } michael@0: michael@0: bool michael@0: Scriptability::IsImmuneToScriptPolicy() michael@0: { michael@0: return mImmuneToScriptPolicy; michael@0: } michael@0: michael@0: void michael@0: Scriptability::Block() michael@0: { michael@0: ++mScriptBlocks; michael@0: } michael@0: michael@0: void michael@0: Scriptability::Unblock() michael@0: { michael@0: MOZ_ASSERT(mScriptBlocks > 0); michael@0: --mScriptBlocks; michael@0: } michael@0: michael@0: void michael@0: Scriptability::SetDocShellAllowsScript(bool aAllowed) michael@0: { michael@0: mDocShellAllowsScript = aAllowed || mImmuneToScriptPolicy; michael@0: } michael@0: michael@0: /* static */ michael@0: Scriptability& michael@0: Scriptability::Get(JSObject *aScope) michael@0: { michael@0: return EnsureCompartmentPrivate(aScope)->scriptability; michael@0: } michael@0: michael@0: bool michael@0: IsXBLScope(JSCompartment *compartment) michael@0: { michael@0: // We always eagerly create compartment privates for XBL scopes. michael@0: CompartmentPrivate *priv = GetCompartmentPrivate(compartment); michael@0: if (!priv || !priv->scope) michael@0: return false; michael@0: return priv->scope->IsXBLScope(); michael@0: } michael@0: michael@0: bool michael@0: IsInXBLScope(JSObject *obj) michael@0: { michael@0: return IsXBLScope(js::GetObjectCompartment(obj)); michael@0: } michael@0: michael@0: bool michael@0: IsUniversalXPConnectEnabled(JSCompartment *compartment) michael@0: { michael@0: CompartmentPrivate *priv = GetCompartmentPrivate(compartment); michael@0: if (!priv) michael@0: return false; michael@0: return priv->universalXPConnectEnabled; michael@0: } michael@0: michael@0: bool michael@0: IsUniversalXPConnectEnabled(JSContext *cx) michael@0: { michael@0: JSCompartment *compartment = js::GetContextCompartment(cx); michael@0: if (!compartment) michael@0: return false; michael@0: return IsUniversalXPConnectEnabled(compartment); michael@0: } michael@0: michael@0: bool michael@0: EnableUniversalXPConnect(JSContext *cx) michael@0: { michael@0: JSCompartment *compartment = js::GetContextCompartment(cx); michael@0: if (!compartment) michael@0: return true; michael@0: // Never set universalXPConnectEnabled on a chrome compartment - it confuses michael@0: // the security wrapping code. michael@0: if (AccessCheck::isChrome(compartment)) michael@0: return true; michael@0: CompartmentPrivate *priv = GetCompartmentPrivate(compartment); michael@0: if (!priv) michael@0: return true; michael@0: priv->universalXPConnectEnabled = true; michael@0: michael@0: // Recompute all the cross-compartment wrappers leaving the newly-privileged michael@0: // compartment. michael@0: bool ok = js::RecomputeWrappers(cx, js::SingleCompartment(compartment), michael@0: js::AllCompartments()); michael@0: NS_ENSURE_TRUE(ok, false); michael@0: michael@0: // The Components object normally isn't defined for unprivileged web content, michael@0: // but we define it when UniversalXPConnect is enabled to support legacy michael@0: // tests. michael@0: XPCWrappedNativeScope *scope = priv->scope; michael@0: if (!scope) michael@0: return true; michael@0: scope->ForcePrivilegedComponents(); michael@0: return scope->AttachComponentsObject(cx); michael@0: } michael@0: michael@0: JSObject * michael@0: GetJunkScope() michael@0: { michael@0: XPCJSRuntime *self = nsXPConnect::GetRuntimeInstance(); michael@0: NS_ENSURE_TRUE(self, nullptr); michael@0: return self->GetJunkScope(); michael@0: } michael@0: michael@0: nsIGlobalObject * michael@0: GetJunkScopeGlobal() michael@0: { michael@0: JSObject *junkScope = GetJunkScope(); michael@0: // GetJunkScope would ideally never fail, currently it is not yet the case michael@0: // unfortunately...(see Bug 874158) michael@0: if (!junkScope) michael@0: return nullptr; michael@0: return GetNativeForGlobal(junkScope); michael@0: } michael@0: michael@0: JSObject * michael@0: GetCompilationScope() michael@0: { michael@0: XPCJSRuntime *self = nsXPConnect::GetRuntimeInstance(); michael@0: NS_ENSURE_TRUE(self, nullptr); michael@0: return self->GetCompilationScope(); michael@0: } michael@0: michael@0: JSObject * michael@0: GetSafeJSContextGlobal() michael@0: { michael@0: return XPCJSRuntime::Get()->GetJSContextStack()->GetSafeJSContextGlobal(); michael@0: } michael@0: michael@0: nsGlobalWindow* michael@0: WindowOrNull(JSObject *aObj) michael@0: { michael@0: MOZ_ASSERT(aObj); michael@0: MOZ_ASSERT(!js::IsWrapper(aObj)); michael@0: michael@0: // This will always return null until we have Window on WebIDL bindings, michael@0: // at which point it will do the right thing. michael@0: if (!IS_WN_CLASS(js::GetObjectClass(aObj))) { michael@0: nsGlobalWindow* win = nullptr; michael@0: UNWRAP_OBJECT(Window, aObj, win); michael@0: return win; michael@0: } michael@0: michael@0: nsISupports* supports = XPCWrappedNative::Get(aObj)->GetIdentityObject(); michael@0: nsCOMPtr piWin = do_QueryInterface(supports); michael@0: if (!piWin) michael@0: return nullptr; michael@0: return static_cast(piWin.get()); michael@0: } michael@0: michael@0: nsGlobalWindow* michael@0: WindowGlobalOrNull(JSObject *aObj) michael@0: { michael@0: MOZ_ASSERT(aObj); michael@0: JSObject *glob = js::GetGlobalForObjectCrossCompartment(aObj); michael@0: michael@0: return WindowOrNull(glob); michael@0: } michael@0: michael@0: } michael@0: michael@0: static void michael@0: CompartmentDestroyedCallback(JSFreeOp *fop, JSCompartment *compartment) michael@0: { michael@0: // NB - This callback may be called in JS_DestroyRuntime, which happens michael@0: // after the XPCJSRuntime has been torn down. michael@0: michael@0: // Get the current compartment private into an AutoPtr (which will do the michael@0: // cleanup for us), and null out the private (which may already be null). michael@0: nsAutoPtr priv(GetCompartmentPrivate(compartment)); michael@0: JS_SetCompartmentPrivate(compartment, nullptr); michael@0: } michael@0: michael@0: void XPCJSRuntime::TraceNativeBlackRoots(JSTracer* trc) michael@0: { michael@0: // Skip this part if XPConnect is shutting down. We get into michael@0: // bad locking problems with the thread iteration otherwise. michael@0: if (!nsXPConnect::XPConnect()->IsShuttingDown()) { michael@0: // Trace those AutoMarkingPtr lists! michael@0: if (AutoMarkingPtr *roots = Get()->mAutoRoots) michael@0: roots->TraceJSAll(trc); michael@0: } michael@0: michael@0: // XPCJSObjectHolders don't participate in cycle collection, so always michael@0: // trace them here. michael@0: XPCRootSetElem *e; michael@0: for (e = mObjectHolderRoots; e; e = e->GetNextRoot()) michael@0: static_cast(e)->TraceJS(trc); michael@0: michael@0: dom::TraceBlackJS(trc, JS_GetGCParameter(Runtime(), JSGC_NUMBER), michael@0: nsXPConnect::XPConnect()->IsShuttingDown()); michael@0: } michael@0: michael@0: void XPCJSRuntime::TraceAdditionalNativeGrayRoots(JSTracer *trc) michael@0: { michael@0: XPCWrappedNativeScope::TraceWrappedNativesInAllScopes(trc, this); michael@0: michael@0: for (XPCRootSetElem *e = mVariantRoots; e ; e = e->GetNextRoot()) michael@0: static_cast(e)->TraceJS(trc); michael@0: michael@0: for (XPCRootSetElem *e = mWrappedJSRoots; e ; e = e->GetNextRoot()) michael@0: static_cast(e)->TraceJS(trc); michael@0: } michael@0: michael@0: // static michael@0: void michael@0: XPCJSRuntime::SuspectWrappedNative(XPCWrappedNative *wrapper, michael@0: nsCycleCollectionNoteRootCallback &cb) michael@0: { michael@0: if (!wrapper->IsValid() || wrapper->IsWrapperExpired()) michael@0: return; michael@0: michael@0: MOZ_ASSERT(NS_IsMainThread(), michael@0: "Suspecting wrapped natives from non-main thread"); michael@0: michael@0: // Only record objects that might be part of a cycle as roots, unless michael@0: // the callback wants all traces (a debug feature). michael@0: JSObject* obj = wrapper->GetFlatJSObjectPreserveColor(); michael@0: if (xpc_IsGrayGCThing(obj) || cb.WantAllTraces()) michael@0: cb.NoteJSRoot(obj); michael@0: } michael@0: michael@0: void michael@0: XPCJSRuntime::TraverseAdditionalNativeRoots(nsCycleCollectionNoteRootCallback &cb) michael@0: { michael@0: XPCWrappedNativeScope::SuspectAllWrappers(this, cb); michael@0: michael@0: for (XPCRootSetElem *e = mVariantRoots; e ; e = e->GetNextRoot()) { michael@0: XPCTraceableVariant* v = static_cast(e); michael@0: if (nsCCUncollectableMarker::InGeneration(cb, michael@0: v->CCGeneration())) { michael@0: jsval val = v->GetJSValPreserveColor(); michael@0: if (val.isObject() && !xpc_IsGrayGCThing(&val.toObject())) michael@0: continue; michael@0: } michael@0: cb.NoteXPCOMRoot(v); michael@0: } michael@0: michael@0: for (XPCRootSetElem *e = mWrappedJSRoots; e ; e = e->GetNextRoot()) { michael@0: cb.NoteXPCOMRoot(ToSupports(static_cast(e))); michael@0: } michael@0: } michael@0: michael@0: void michael@0: XPCJSRuntime::UnmarkSkippableJSHolders() michael@0: { michael@0: CycleCollectedJSRuntime::UnmarkSkippableJSHolders(); michael@0: } michael@0: michael@0: void michael@0: XPCJSRuntime::PrepareForForgetSkippable() michael@0: { michael@0: nsCOMPtr obs = mozilla::services::GetObserverService(); michael@0: if (obs) { michael@0: obs->NotifyObservers(nullptr, "cycle-collector-forget-skippable", nullptr); michael@0: } michael@0: } michael@0: michael@0: void michael@0: XPCJSRuntime::BeginCycleCollectionCallback() michael@0: { michael@0: nsJSContext::BeginCycleCollectionCallback(); michael@0: michael@0: nsCOMPtr obs = mozilla::services::GetObserverService(); michael@0: if (obs) { michael@0: obs->NotifyObservers(nullptr, "cycle-collector-begin", nullptr); michael@0: } michael@0: } michael@0: michael@0: void michael@0: XPCJSRuntime::EndCycleCollectionCallback(CycleCollectorResults &aResults) michael@0: { michael@0: nsJSContext::EndCycleCollectionCallback(aResults); michael@0: michael@0: nsCOMPtr obs = mozilla::services::GetObserverService(); michael@0: if (obs) { michael@0: obs->NotifyObservers(nullptr, "cycle-collector-end", nullptr); michael@0: } michael@0: } michael@0: michael@0: void michael@0: XPCJSRuntime::DispatchDeferredDeletion(bool aContinuation) michael@0: { michael@0: mAsyncSnowWhiteFreer->Dispatch(aContinuation); michael@0: } michael@0: michael@0: void michael@0: xpc_UnmarkSkippableJSHolders() michael@0: { michael@0: if (nsXPConnect::XPConnect()->GetRuntime()) { michael@0: nsXPConnect::XPConnect()->GetRuntime()->UnmarkSkippableJSHolders(); michael@0: } michael@0: } michael@0: michael@0: template static void michael@0: DoDeferredRelease(nsTArray &array) michael@0: { michael@0: while (1) { michael@0: uint32_t count = array.Length(); michael@0: if (!count) { michael@0: array.Compact(); michael@0: break; michael@0: } michael@0: T wrapper = array[count-1]; michael@0: array.RemoveElementAt(count-1); michael@0: NS_RELEASE(wrapper); michael@0: } michael@0: } michael@0: michael@0: /* static */ void michael@0: XPCJSRuntime::GCSliceCallback(JSRuntime *rt, michael@0: JS::GCProgress progress, michael@0: const JS::GCDescription &desc) michael@0: { michael@0: XPCJSRuntime *self = nsXPConnect::GetRuntimeInstance(); michael@0: if (!self) michael@0: return; michael@0: michael@0: #ifdef MOZ_CRASHREPORTER michael@0: CrashReporter::SetGarbageCollecting(progress == JS::GC_CYCLE_BEGIN || michael@0: progress == JS::GC_SLICE_BEGIN); michael@0: #endif michael@0: michael@0: if (self->mPrevGCSliceCallback) michael@0: (*self->mPrevGCSliceCallback)(rt, progress, desc); michael@0: } michael@0: michael@0: void michael@0: XPCJSRuntime::CustomGCCallback(JSGCStatus status) michael@0: { michael@0: // Record kLivingAdopters once per GC to approximate time sampling during michael@0: // periods of activity. michael@0: if (status == JSGC_BEGIN) { michael@0: Telemetry::Accumulate(Telemetry::COMPARTMENT_LIVING_ADOPTERS, michael@0: kLivingAdopters); michael@0: } michael@0: michael@0: nsTArray callbacks(extraGCCallbacks); michael@0: for (uint32_t i = 0; i < callbacks.Length(); ++i) michael@0: callbacks[i](status); michael@0: } michael@0: michael@0: /* static */ void michael@0: XPCJSRuntime::FinalizeCallback(JSFreeOp *fop, JSFinalizeStatus status, bool isCompartmentGC) michael@0: { michael@0: XPCJSRuntime* self = nsXPConnect::GetRuntimeInstance(); michael@0: if (!self) michael@0: return; michael@0: michael@0: switch (status) { michael@0: case JSFINALIZE_GROUP_START: michael@0: { michael@0: MOZ_ASSERT(!self->mDoingFinalization, "bad state"); michael@0: michael@0: MOZ_ASSERT(!self->mGCIsRunning, "bad state"); michael@0: self->mGCIsRunning = true; michael@0: michael@0: nsTArray* dyingWrappedJSArray = michael@0: &self->mWrappedJSToReleaseArray; michael@0: michael@0: // Add any wrappers whose JSObjects are to be finalized to michael@0: // this array. Note that we do not want to be changing the michael@0: // refcount of these wrappers. michael@0: // We add them to the array now and Release the array members michael@0: // later to avoid the posibility of doing any JS GCThing michael@0: // allocations during the gc cycle. michael@0: self->mWrappedJSMap->FindDyingJSObjects(dyingWrappedJSArray); michael@0: michael@0: // Find dying scopes. michael@0: XPCWrappedNativeScope::StartFinalizationPhaseOfGC(fop, self); michael@0: michael@0: self->mDoingFinalization = true; michael@0: break; michael@0: } michael@0: case JSFINALIZE_GROUP_END: michael@0: { michael@0: MOZ_ASSERT(self->mDoingFinalization, "bad state"); michael@0: self->mDoingFinalization = false; michael@0: michael@0: // Release all the members whose JSObjects are now known michael@0: // to be dead. michael@0: DoDeferredRelease(self->mWrappedJSToReleaseArray); michael@0: michael@0: // Sweep scopes needing cleanup michael@0: XPCWrappedNativeScope::FinishedFinalizationPhaseOfGC(); michael@0: michael@0: MOZ_ASSERT(self->mGCIsRunning, "bad state"); michael@0: self->mGCIsRunning = false; michael@0: michael@0: break; michael@0: } michael@0: case JSFINALIZE_COLLECTION_END: michael@0: { michael@0: MOZ_ASSERT(!self->mGCIsRunning, "bad state"); michael@0: self->mGCIsRunning = true; michael@0: michael@0: // We use this occasion to mark and sweep NativeInterfaces, michael@0: // NativeSets, and the WrappedNativeJSClasses... michael@0: michael@0: // Do the marking... michael@0: XPCWrappedNativeScope::MarkAllWrappedNativesAndProtos(); michael@0: michael@0: self->mDetachedWrappedNativeProtoMap-> michael@0: Enumerate(DetachedWrappedNativeProtoMarker, nullptr); michael@0: michael@0: DOM_MarkInterfaces(); michael@0: michael@0: // Mark the sets used in the call contexts. There is a small michael@0: // chance that a wrapper's set will change *while* a call is michael@0: // happening which uses that wrapper's old interfface set. So, michael@0: // we need to do this marking to avoid collecting those sets michael@0: // that might no longer be otherwise reachable from the wrappers michael@0: // or the wrapperprotos. michael@0: michael@0: // Skip this part if XPConnect is shutting down. We get into michael@0: // bad locking problems with the thread iteration otherwise. michael@0: if (!nsXPConnect::XPConnect()->IsShuttingDown()) { michael@0: michael@0: // Mark those AutoMarkingPtr lists! michael@0: if (AutoMarkingPtr *roots = Get()->mAutoRoots) michael@0: roots->MarkAfterJSFinalizeAll(); michael@0: michael@0: XPCCallContext* ccxp = XPCJSRuntime::Get()->GetCallContext(); michael@0: while (ccxp) { michael@0: // Deal with the strictness of callcontext that michael@0: // complains if you ask for a set when michael@0: // it is in a state where the set could not michael@0: // possibly be valid. michael@0: if (ccxp->CanGetSet()) { michael@0: XPCNativeSet* set = ccxp->GetSet(); michael@0: if (set) michael@0: set->Mark(); michael@0: } michael@0: if (ccxp->CanGetInterface()) { michael@0: XPCNativeInterface* iface = ccxp->GetInterface(); michael@0: if (iface) michael@0: iface->Mark(); michael@0: } michael@0: ccxp = ccxp->GetPrevCallContext(); michael@0: } michael@0: } michael@0: michael@0: // Do the sweeping. During a compartment GC, only michael@0: // WrappedNativeProtos in collected compartments will be michael@0: // marked. Therefore, some reachable NativeInterfaces will not be michael@0: // marked, so it is not safe to sweep them. We still need to unmark michael@0: // them, since the ones pointed to by WrappedNativeProtos in a michael@0: // compartment being collected will be marked. michael@0: // michael@0: // Ideally, if NativeInterfaces from different compartments were michael@0: // kept separate, we could sweep only the ones belonging to michael@0: // compartments being collected. Currently, though, NativeInterfaces michael@0: // are shared between compartments. This ought to be fixed. michael@0: void *sweepArg = isCompartmentGC ? UNMARK_ONLY : UNMARK_AND_SWEEP; michael@0: michael@0: // We don't want to sweep the JSClasses at shutdown time. michael@0: // At this point there may be JSObjects using them that have michael@0: // been removed from the other maps. michael@0: if (!nsXPConnect::XPConnect()->IsShuttingDown()) { michael@0: self->mNativeScriptableSharedMap-> michael@0: Enumerate(JSClassSweeper, sweepArg); michael@0: } michael@0: michael@0: if (!isCompartmentGC) { michael@0: self->mClassInfo2NativeSetMap-> michael@0: Enumerate(NativeUnMarkedSetRemover, nullptr); michael@0: } michael@0: michael@0: self->mNativeSetMap-> michael@0: Enumerate(NativeSetSweeper, sweepArg); michael@0: michael@0: self->mIID2NativeInterfaceMap-> michael@0: Enumerate(NativeInterfaceSweeper, sweepArg); michael@0: michael@0: #ifdef DEBUG michael@0: XPCWrappedNativeScope::ASSERT_NoInterfaceSetsAreMarked(); michael@0: #endif michael@0: michael@0: // Now we are going to recycle any unused WrappedNativeTearoffs. michael@0: // We do this by iterating all the live callcontexts michael@0: // and marking the tearoffs in use. And then we michael@0: // iterate over all the WrappedNative wrappers and sweep their michael@0: // tearoffs. michael@0: // michael@0: // This allows us to perhaps minimize the growth of the michael@0: // tearoffs. And also makes us not hold references to interfaces michael@0: // on our wrapped natives that we are not actually using. michael@0: // michael@0: // XXX We may decide to not do this on *every* gc cycle. michael@0: michael@0: // Skip this part if XPConnect is shutting down. We get into michael@0: // bad locking problems with the thread iteration otherwise. michael@0: if (!nsXPConnect::XPConnect()->IsShuttingDown()) { michael@0: // Do the marking... michael@0: michael@0: XPCCallContext* ccxp = XPCJSRuntime::Get()->GetCallContext(); michael@0: while (ccxp) { michael@0: // Deal with the strictness of callcontext that michael@0: // complains if you ask for a tearoff when michael@0: // it is in a state where the tearoff could not michael@0: // possibly be valid. michael@0: if (ccxp->CanGetTearOff()) { michael@0: XPCWrappedNativeTearOff* to = michael@0: ccxp->GetTearOff(); michael@0: if (to) michael@0: to->Mark(); michael@0: } michael@0: ccxp = ccxp->GetPrevCallContext(); michael@0: } michael@0: michael@0: // Do the sweeping... michael@0: XPCWrappedNativeScope::SweepAllWrappedNativeTearOffs(); michael@0: } michael@0: michael@0: // Now we need to kill the 'Dying' XPCWrappedNativeProtos. michael@0: // We transfered these native objects to this table when their michael@0: // JSObject's were finalized. We did not destroy them immediately michael@0: // at that point because the ordering of JS finalization is not michael@0: // deterministic and we did not yet know if any wrappers that michael@0: // might still be referencing the protos where still yet to be michael@0: // finalized and destroyed. We *do* know that the protos' michael@0: // JSObjects would not have been finalized if there were any michael@0: // wrappers that referenced the proto but where not themselves michael@0: // slated for finalization in this gc cycle. So... at this point michael@0: // we know that any and all wrappers that might have been michael@0: // referencing the protos in the dying list are themselves dead. michael@0: // So, we can safely delete all the protos in the list. michael@0: michael@0: self->mDyingWrappedNativeProtoMap-> michael@0: Enumerate(DyingProtoKiller, nullptr); michael@0: michael@0: MOZ_ASSERT(self->mGCIsRunning, "bad state"); michael@0: self->mGCIsRunning = false; michael@0: michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: static void WatchdogMain(void *arg); michael@0: class Watchdog; michael@0: class WatchdogManager; michael@0: class AutoLockWatchdog { michael@0: Watchdog* const mWatchdog; michael@0: public: michael@0: AutoLockWatchdog(Watchdog* aWatchdog); michael@0: ~AutoLockWatchdog(); michael@0: }; michael@0: michael@0: class Watchdog michael@0: { michael@0: public: michael@0: Watchdog(WatchdogManager *aManager) michael@0: : mManager(aManager) michael@0: , mLock(nullptr) michael@0: , mWakeup(nullptr) michael@0: , mThread(nullptr) michael@0: , mHibernating(false) michael@0: , mInitialized(false) michael@0: , mShuttingDown(false) michael@0: {} michael@0: ~Watchdog() { MOZ_ASSERT(!Initialized()); } michael@0: michael@0: WatchdogManager* Manager() { return mManager; } michael@0: bool Initialized() { return mInitialized; } michael@0: bool ShuttingDown() { return mShuttingDown; } michael@0: PRLock *GetLock() { return mLock; } michael@0: bool Hibernating() { return mHibernating; } michael@0: void WakeUp() michael@0: { michael@0: MOZ_ASSERT(Initialized()); michael@0: MOZ_ASSERT(Hibernating()); michael@0: mHibernating = false; michael@0: PR_NotifyCondVar(mWakeup); michael@0: } michael@0: michael@0: // michael@0: // Invoked by the main thread only. michael@0: // michael@0: michael@0: void Init() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: mLock = PR_NewLock(); michael@0: if (!mLock) michael@0: NS_RUNTIMEABORT("PR_NewLock failed."); michael@0: mWakeup = PR_NewCondVar(mLock); michael@0: if (!mWakeup) michael@0: NS_RUNTIMEABORT("PR_NewCondVar failed."); michael@0: michael@0: { michael@0: AutoLockWatchdog lock(this); michael@0: michael@0: mThread = PR_CreateThread(PR_USER_THREAD, WatchdogMain, this, michael@0: PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, michael@0: PR_UNJOINABLE_THREAD, 0); michael@0: if (!mThread) michael@0: NS_RUNTIMEABORT("PR_CreateThread failed!"); michael@0: michael@0: // WatchdogMain acquires the lock and then asserts mInitialized. So michael@0: // make sure to set mInitialized before releasing the lock here so michael@0: // that it's atomic with the creation of the thread. michael@0: mInitialized = true; michael@0: } michael@0: } michael@0: michael@0: void Shutdown() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: MOZ_ASSERT(Initialized()); michael@0: { // Scoped lock. michael@0: AutoLockWatchdog lock(this); michael@0: michael@0: // Signal to the watchdog thread that it's time to shut down. michael@0: mShuttingDown = true; michael@0: michael@0: // Wake up the watchdog, and wait for it to call us back. michael@0: PR_NotifyCondVar(mWakeup); michael@0: PR_WaitCondVar(mWakeup, PR_INTERVAL_NO_TIMEOUT); michael@0: MOZ_ASSERT(!mShuttingDown); michael@0: } michael@0: michael@0: // Destroy state. michael@0: mThread = nullptr; michael@0: PR_DestroyCondVar(mWakeup); michael@0: mWakeup = nullptr; michael@0: PR_DestroyLock(mLock); michael@0: mLock = nullptr; michael@0: michael@0: // All done. michael@0: mInitialized = false; michael@0: } michael@0: michael@0: // michael@0: // Invoked by the watchdog thread only. michael@0: // michael@0: michael@0: void Hibernate() michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread()); michael@0: mHibernating = true; michael@0: Sleep(PR_INTERVAL_NO_TIMEOUT); michael@0: } michael@0: void Sleep(PRIntervalTime timeout) michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread()); michael@0: MOZ_ALWAYS_TRUE(PR_WaitCondVar(mWakeup, timeout) == PR_SUCCESS); michael@0: } michael@0: void Finished() michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread()); michael@0: mShuttingDown = false; michael@0: PR_NotifyCondVar(mWakeup); michael@0: } michael@0: michael@0: private: michael@0: WatchdogManager *mManager; michael@0: michael@0: PRLock *mLock; michael@0: PRCondVar *mWakeup; michael@0: PRThread *mThread; michael@0: bool mHibernating; michael@0: bool mInitialized; michael@0: bool mShuttingDown; michael@0: }; michael@0: michael@0: #ifdef MOZ_NUWA_PROCESS michael@0: #include "ipc/Nuwa.h" michael@0: #endif michael@0: michael@0: class WatchdogManager : public nsIObserver michael@0: { michael@0: public: michael@0: michael@0: NS_DECL_ISUPPORTS michael@0: WatchdogManager(XPCJSRuntime *aRuntime) : mRuntime(aRuntime) michael@0: , mRuntimeState(RUNTIME_INACTIVE) michael@0: { michael@0: // All the timestamps start at zero except for runtime state change. michael@0: PodArrayZero(mTimestamps); michael@0: mTimestamps[TimestampRuntimeStateChange] = PR_Now(); michael@0: michael@0: // Enable the watchdog, if appropriate. michael@0: RefreshWatchdog(); michael@0: michael@0: // Register ourselves as an observer to get updates on the pref. michael@0: mozilla::Preferences::AddStrongObserver(this, "dom.use_watchdog"); michael@0: } michael@0: virtual ~WatchdogManager() michael@0: { michael@0: // Shutting down the watchdog requires context-switching to the watchdog michael@0: // thread, which isn't great to do in a destructor. So we require michael@0: // consumers to shut it down manually before releasing it. michael@0: MOZ_ASSERT(!mWatchdog); michael@0: mozilla::Preferences::RemoveObserver(this, "dom.use_watchdog"); michael@0: } michael@0: michael@0: NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic, michael@0: const char16_t* aData) michael@0: { michael@0: RefreshWatchdog(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Runtime statistics. These live on the watchdog manager, are written michael@0: // from the main thread, and are read from the watchdog thread (holding michael@0: // the lock in each case). michael@0: void michael@0: RecordRuntimeActivity(bool active) michael@0: { michael@0: // The watchdog reads this state, so acquire the lock before writing it. michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: Maybe lock; michael@0: if (mWatchdog) michael@0: lock.construct(mWatchdog); michael@0: michael@0: // Write state. michael@0: mTimestamps[TimestampRuntimeStateChange] = PR_Now(); michael@0: mRuntimeState = active ? RUNTIME_ACTIVE : RUNTIME_INACTIVE; michael@0: michael@0: // The watchdog may be hibernating, waiting for the runtime to go michael@0: // active. Wake it up if necessary. michael@0: if (active && mWatchdog && mWatchdog->Hibernating()) michael@0: mWatchdog->WakeUp(); michael@0: } michael@0: bool IsRuntimeActive() { return mRuntimeState == RUNTIME_ACTIVE; } michael@0: PRTime TimeSinceLastRuntimeStateChange() michael@0: { michael@0: return PR_Now() - GetTimestamp(TimestampRuntimeStateChange); michael@0: } michael@0: michael@0: // Note - Because of the runtime activity timestamp, these are read and michael@0: // written from both threads. michael@0: void RecordTimestamp(WatchdogTimestampCategory aCategory) michael@0: { michael@0: // The watchdog thread always holds the lock when it runs. michael@0: Maybe maybeLock; michael@0: if (NS_IsMainThread() && mWatchdog) michael@0: maybeLock.construct(mWatchdog); michael@0: mTimestamps[aCategory] = PR_Now(); michael@0: } michael@0: PRTime GetTimestamp(WatchdogTimestampCategory aCategory) michael@0: { michael@0: // The watchdog thread always holds the lock when it runs. michael@0: Maybe maybeLock; michael@0: if (NS_IsMainThread() && mWatchdog) michael@0: maybeLock.construct(mWatchdog); michael@0: return mTimestamps[aCategory]; michael@0: } michael@0: michael@0: XPCJSRuntime* Runtime() { return mRuntime; } michael@0: Watchdog* GetWatchdog() { return mWatchdog; } michael@0: michael@0: void RefreshWatchdog() michael@0: { michael@0: bool wantWatchdog = Preferences::GetBool("dom.use_watchdog", true); michael@0: if (wantWatchdog == !!mWatchdog) michael@0: return; michael@0: if (wantWatchdog) michael@0: StartWatchdog(); michael@0: else michael@0: StopWatchdog(); michael@0: } michael@0: michael@0: void StartWatchdog() michael@0: { michael@0: MOZ_ASSERT(!mWatchdog); michael@0: mWatchdog = new Watchdog(this); michael@0: mWatchdog->Init(); michael@0: } michael@0: michael@0: void StopWatchdog() michael@0: { michael@0: MOZ_ASSERT(mWatchdog); michael@0: mWatchdog->Shutdown(); michael@0: mWatchdog = nullptr; michael@0: } michael@0: michael@0: private: michael@0: XPCJSRuntime *mRuntime; michael@0: nsAutoPtr mWatchdog; michael@0: michael@0: enum { RUNTIME_ACTIVE, RUNTIME_INACTIVE } mRuntimeState; michael@0: PRTime mTimestamps[TimestampCount]; michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(WatchdogManager, nsIObserver) michael@0: michael@0: AutoLockWatchdog::AutoLockWatchdog(Watchdog *aWatchdog) : mWatchdog(aWatchdog) michael@0: { michael@0: PR_Lock(mWatchdog->GetLock()); michael@0: } michael@0: michael@0: AutoLockWatchdog::~AutoLockWatchdog() michael@0: { michael@0: PR_Unlock(mWatchdog->GetLock()); michael@0: } michael@0: michael@0: static void michael@0: WatchdogMain(void *arg) michael@0: { michael@0: PR_SetCurrentThreadName("JS Watchdog"); michael@0: michael@0: #ifdef MOZ_NUWA_PROCESS michael@0: if (IsNuwaProcess()) { michael@0: NS_ASSERTION(NuwaMarkCurrentThread != nullptr, michael@0: "NuwaMarkCurrentThread is undefined!"); michael@0: NuwaMarkCurrentThread(nullptr, nullptr); michael@0: NuwaFreezeCurrentThread(); michael@0: } michael@0: #endif michael@0: michael@0: Watchdog* self = static_cast(arg); michael@0: WatchdogManager* manager = self->Manager(); michael@0: michael@0: // Lock lasts until we return michael@0: AutoLockWatchdog lock(self); michael@0: michael@0: MOZ_ASSERT(self->Initialized()); michael@0: MOZ_ASSERT(!self->ShuttingDown()); michael@0: while (!self->ShuttingDown()) { michael@0: // Sleep only 1 second if recently (or currently) active; otherwise, hibernate michael@0: if (manager->IsRuntimeActive() || michael@0: manager->TimeSinceLastRuntimeStateChange() <= PRTime(2*PR_USEC_PER_SEC)) michael@0: { michael@0: self->Sleep(PR_TicksPerSecond()); michael@0: } else { michael@0: manager->RecordTimestamp(TimestampWatchdogHibernateStart); michael@0: self->Hibernate(); michael@0: manager->RecordTimestamp(TimestampWatchdogHibernateStop); michael@0: } michael@0: michael@0: // Rise and shine. michael@0: manager->RecordTimestamp(TimestampWatchdogWakeup); michael@0: michael@0: // Don't request an interrupt callback if activity started less than one second ago. michael@0: // The callback is only used for detecting long running scripts, and triggering the michael@0: // callback from off the main thread can be expensive. michael@0: if (manager->IsRuntimeActive() && michael@0: manager->TimeSinceLastRuntimeStateChange() >= PRTime(PR_USEC_PER_SEC)) michael@0: { michael@0: bool debuggerAttached = false; michael@0: nsCOMPtr dbg = do_GetService("@mozilla.org/xpcom/debug;1"); michael@0: if (dbg) michael@0: dbg->GetIsDebuggerAttached(&debuggerAttached); michael@0: if (!debuggerAttached) michael@0: JS_RequestInterruptCallback(manager->Runtime()->Runtime()); michael@0: } michael@0: } michael@0: michael@0: // Tell the manager that we've shut down. michael@0: self->Finished(); michael@0: } michael@0: michael@0: PRTime michael@0: XPCJSRuntime::GetWatchdogTimestamp(WatchdogTimestampCategory aCategory) michael@0: { michael@0: return mWatchdogManager->GetTimestamp(aCategory); michael@0: } michael@0: michael@0: void michael@0: xpc::SimulateActivityCallback(bool aActive) michael@0: { michael@0: XPCJSRuntime::ActivityCallback(XPCJSRuntime::Get(), aActive); michael@0: } michael@0: michael@0: // static michael@0: JSContext* michael@0: XPCJSRuntime::DefaultJSContextCallback(JSRuntime *rt) michael@0: { michael@0: MOZ_ASSERT(rt == Get()->Runtime()); michael@0: return Get()->GetJSContextStack()->GetSafeJSContext(); michael@0: } michael@0: michael@0: // static michael@0: void michael@0: XPCJSRuntime::ActivityCallback(void *arg, bool active) michael@0: { michael@0: XPCJSRuntime* self = static_cast(arg); michael@0: self->mWatchdogManager->RecordRuntimeActivity(active); michael@0: } michael@0: michael@0: // static michael@0: // michael@0: // JS-CTypes creates and caches a JSContext that it uses when executing JS michael@0: // callbacks. When we're notified that ctypes is about to call into some JS, michael@0: // push the cx to maintain the integrity of the context stack. michael@0: void michael@0: XPCJSRuntime::CTypesActivityCallback(JSContext *cx, js::CTypesActivityType type) michael@0: { michael@0: if (type == js::CTYPES_CALLBACK_BEGIN) { michael@0: if (!xpc::PushJSContextNoScriptContext(cx)) michael@0: MOZ_CRASH(); michael@0: } else if (type == js::CTYPES_CALLBACK_END) { michael@0: xpc::PopJSContextNoScriptContext(); michael@0: } michael@0: } michael@0: michael@0: // static michael@0: bool michael@0: XPCJSRuntime::InterruptCallback(JSContext *cx) michael@0: { michael@0: XPCJSRuntime *self = XPCJSRuntime::Get(); michael@0: michael@0: // If this is the first time the interrupt callback has fired since we last michael@0: // returned to the event loop, mark the checkpoint. michael@0: if (self->mSlowScriptCheckpoint.IsNull()) { michael@0: self->mSlowScriptCheckpoint = TimeStamp::NowLoRes(); michael@0: return true; michael@0: } michael@0: michael@0: // This is at least the second interrupt callback we've received since michael@0: // returning to the event loop. See how long it's been, and what the limit michael@0: // is. michael@0: TimeDuration duration = TimeStamp::NowLoRes() - self->mSlowScriptCheckpoint; michael@0: bool chrome = michael@0: nsContentUtils::IsSystemPrincipal(nsContentUtils::GetSubjectPrincipal()); michael@0: const char *prefName = chrome ? "dom.max_chrome_script_run_time" michael@0: : "dom.max_script_run_time"; michael@0: int32_t limit = Preferences::GetInt(prefName, chrome ? 20 : 10); michael@0: michael@0: // If there's no limit, or we're within the limit, let it go. michael@0: if (limit == 0 || duration.ToSeconds() < limit) michael@0: return true; michael@0: michael@0: // michael@0: // This has gone on long enough! Time to take action. ;-) michael@0: // michael@0: michael@0: // Get the DOM window associated with the running script. If the script is michael@0: // running in a non-DOM scope, we have to just let it keep running. michael@0: RootedObject global(cx, JS::CurrentGlobalOrNull(cx)); michael@0: nsRefPtr win = WindowOrNull(global); michael@0: if (!win && IsSandbox(global)) { michael@0: // If this is a sandbox associated with a DOMWindow via a michael@0: // sandboxPrototype, use that DOMWindow. This supports GreaseMonkey michael@0: // and JetPack content scripts. michael@0: JS::Rooted proto(cx); michael@0: if (!JS_GetPrototype(cx, global, &proto)) michael@0: return false; michael@0: if (proto && IsSandboxPrototypeProxy(proto) && michael@0: (proto = js::CheckedUnwrap(proto, /* stopAtOuter = */ false))) michael@0: { michael@0: win = WindowGlobalOrNull(proto); michael@0: } michael@0: } michael@0: if (!win) michael@0: return true; michael@0: michael@0: // Show the prompt to the user, and kill if requested. michael@0: nsGlobalWindow::SlowScriptResponse response = win->ShowSlowScriptDialog(); michael@0: if (response == nsGlobalWindow::KillSlowScript) michael@0: return false; michael@0: michael@0: // The user chose to continue the script. Reset the timer, and disable this michael@0: // machinery with a pref of the user opted out of future slow-script dialogs. michael@0: self->mSlowScriptCheckpoint = TimeStamp::NowLoRes(); michael@0: if (response == nsGlobalWindow::AlwaysContinueSlowScript) michael@0: Preferences::SetInt(prefName, 0); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: /* static */ void michael@0: XPCJSRuntime::OutOfMemoryCallback(JSContext *cx) michael@0: { michael@0: if (!Preferences::GetBool("memory.dump_reports_on_oom")) { michael@0: return; michael@0: } michael@0: michael@0: nsCOMPtr dumper = michael@0: do_GetService("@mozilla.org/memory-info-dumper;1"); michael@0: if (!dumper) { michael@0: return; michael@0: } michael@0: michael@0: // If this fails, it fails silently. michael@0: dumper->DumpMemoryInfoToTempDir(NS_LITERAL_STRING("due-to-JS-OOM"), michael@0: /* minimizeMemoryUsage = */ false); michael@0: } michael@0: michael@0: size_t michael@0: XPCJSRuntime::SizeOfIncludingThis(MallocSizeOf mallocSizeOf) michael@0: { michael@0: size_t n = 0; michael@0: n += mallocSizeOf(this); michael@0: n += mWrappedJSMap->SizeOfIncludingThis(mallocSizeOf); michael@0: n += mIID2NativeInterfaceMap->SizeOfIncludingThis(mallocSizeOf); michael@0: n += mClassInfo2NativeSetMap->ShallowSizeOfIncludingThis(mallocSizeOf); michael@0: n += mNativeSetMap->SizeOfIncludingThis(mallocSizeOf); michael@0: michael@0: n += CycleCollectedJSRuntime::SizeOfExcludingThis(mallocSizeOf); michael@0: michael@0: // There are other XPCJSRuntime members that could be measured; the above michael@0: // ones have been seen by DMD to be worth measuring. More stuff may be michael@0: // added later. michael@0: michael@0: return n; michael@0: } michael@0: michael@0: nsString* michael@0: XPCJSRuntime::NewShortLivedString() michael@0: { michael@0: for (uint32_t i = 0; i < XPCCCX_STRING_CACHE_SIZE; ++i) { michael@0: if (mScratchStrings[i].empty()) { michael@0: mScratchStrings[i].construct(); michael@0: return mScratchStrings[i].addr(); michael@0: } michael@0: } michael@0: michael@0: // All our internal string wrappers are used, allocate a new string. michael@0: return new nsString(); michael@0: } michael@0: michael@0: void michael@0: XPCJSRuntime::DeleteShortLivedString(nsString *string) michael@0: { michael@0: if (string == &EmptyString() || string == &NullString()) michael@0: return; michael@0: michael@0: for (uint32_t i = 0; i < XPCCCX_STRING_CACHE_SIZE; ++i) { michael@0: if (!mScratchStrings[i].empty() && michael@0: mScratchStrings[i].addr() == string) { michael@0: // One of our internal strings is no longer in use, mark michael@0: // it as such and free its data. michael@0: mScratchStrings[i].destroy(); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: // We're done with a string that's not one of our internal michael@0: // strings, delete it. michael@0: delete string; michael@0: } michael@0: michael@0: /***************************************************************************/ michael@0: michael@0: static PLDHashOperator michael@0: DetachedWrappedNativeProtoShutdownMarker(PLDHashTable *table, PLDHashEntryHdr *hdr, michael@0: uint32_t number, void *arg) michael@0: { michael@0: XPCWrappedNativeProto* proto = michael@0: (XPCWrappedNativeProto*)((PLDHashEntryStub*)hdr)->key; michael@0: michael@0: proto->SystemIsBeingShutDown(); michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: void XPCJSRuntime::DestroyJSContextStack() michael@0: { michael@0: delete mJSContextStack; michael@0: mJSContextStack = nullptr; michael@0: } michael@0: michael@0: void XPCJSRuntime::SystemIsBeingShutDown() michael@0: { michael@0: DOM_ClearInterfaces(); michael@0: michael@0: if (mDetachedWrappedNativeProtoMap) michael@0: mDetachedWrappedNativeProtoMap-> michael@0: Enumerate(DetachedWrappedNativeProtoShutdownMarker, nullptr); michael@0: } michael@0: michael@0: #define JS_OPTIONS_DOT_STR "javascript.options." michael@0: michael@0: static void michael@0: ReloadPrefsCallback(const char *pref, void *data) michael@0: { michael@0: XPCJSRuntime *runtime = reinterpret_cast(data); michael@0: JSRuntime *rt = runtime->Runtime(); michael@0: michael@0: bool safeMode = false; michael@0: nsCOMPtr xr = do_GetService("@mozilla.org/xre/runtime;1"); michael@0: if (xr) { michael@0: xr->GetInSafeMode(&safeMode); michael@0: } michael@0: michael@0: bool useBaseline = Preferences::GetBool(JS_OPTIONS_DOT_STR "baselinejit") && !safeMode; michael@0: bool useIon = Preferences::GetBool(JS_OPTIONS_DOT_STR "ion") && !safeMode; michael@0: bool useAsmJS = Preferences::GetBool(JS_OPTIONS_DOT_STR "asmjs") && !safeMode; michael@0: michael@0: bool parallelParsing = Preferences::GetBool(JS_OPTIONS_DOT_STR "parallel_parsing"); michael@0: bool parallelIonCompilation = Preferences::GetBool(JS_OPTIONS_DOT_STR michael@0: "ion.parallel_compilation"); michael@0: bool useBaselineEager = Preferences::GetBool(JS_OPTIONS_DOT_STR michael@0: "baselinejit.unsafe_eager_compilation"); michael@0: bool useIonEager = Preferences::GetBool(JS_OPTIONS_DOT_STR "ion.unsafe_eager_compilation"); michael@0: michael@0: sDiscardSystemSource = Preferences::GetBool(JS_OPTIONS_DOT_STR "discardSystemSource"); michael@0: michael@0: JS::RuntimeOptionsRef(rt).setBaseline(useBaseline) michael@0: .setIon(useIon) michael@0: . setAsmJS(useAsmJS); michael@0: michael@0: JS_SetParallelParsingEnabled(rt, parallelParsing); michael@0: JS_SetParallelIonCompilationEnabled(rt, parallelIonCompilation); michael@0: JS_SetGlobalJitCompilerOption(rt, JSJITCOMPILER_BASELINE_USECOUNT_TRIGGER, michael@0: useBaselineEager ? 0 : -1); michael@0: JS_SetGlobalJitCompilerOption(rt, JSJITCOMPILER_ION_USECOUNT_TRIGGER, michael@0: useIonEager ? 0 : -1); michael@0: } michael@0: michael@0: XPCJSRuntime::~XPCJSRuntime() michael@0: { michael@0: // This destructor runs before ~CycleCollectedJSRuntime, which does the michael@0: // actual JS_DestroyRuntime() call. But destroying the runtime triggers michael@0: // one final GC, which can call back into the runtime with various michael@0: // callback if we aren't careful. Null out the relevant callbacks. michael@0: js::SetActivityCallback(Runtime(), nullptr, nullptr); michael@0: JS_SetFinalizeCallback(Runtime(), nullptr); michael@0: michael@0: // Clear any pending exception. It might be an XPCWrappedJS, and if we try michael@0: // to destroy it later we will crash. michael@0: SetPendingException(nullptr); michael@0: michael@0: JS::SetGCSliceCallback(Runtime(), mPrevGCSliceCallback); michael@0: michael@0: xpc_DelocalizeRuntime(Runtime()); michael@0: michael@0: if (mWatchdogManager->GetWatchdog()) michael@0: mWatchdogManager->StopWatchdog(); michael@0: michael@0: if (mCallContext) michael@0: mCallContext->SystemIsBeingShutDown(); michael@0: michael@0: auto rtPrivate = static_cast(JS_GetRuntimePrivate(Runtime())); michael@0: delete rtPrivate; michael@0: JS_SetRuntimePrivate(Runtime(), nullptr); michael@0: michael@0: // clean up and destroy maps... michael@0: if (mWrappedJSMap) { michael@0: mWrappedJSMap->ShutdownMarker(); michael@0: delete mWrappedJSMap; michael@0: mWrappedJSMap = nullptr; michael@0: } michael@0: michael@0: if (mWrappedJSClassMap) { michael@0: delete mWrappedJSClassMap; michael@0: mWrappedJSClassMap = nullptr; michael@0: } michael@0: michael@0: if (mIID2NativeInterfaceMap) { michael@0: delete mIID2NativeInterfaceMap; michael@0: mIID2NativeInterfaceMap = nullptr; michael@0: } michael@0: michael@0: if (mClassInfo2NativeSetMap) { michael@0: delete mClassInfo2NativeSetMap; michael@0: mClassInfo2NativeSetMap = nullptr; michael@0: } michael@0: michael@0: if (mNativeSetMap) { michael@0: delete mNativeSetMap; michael@0: mNativeSetMap = nullptr; michael@0: } michael@0: michael@0: if (mThisTranslatorMap) { michael@0: delete mThisTranslatorMap; michael@0: mThisTranslatorMap = nullptr; michael@0: } michael@0: michael@0: if (mNativeScriptableSharedMap) { michael@0: delete mNativeScriptableSharedMap; michael@0: mNativeScriptableSharedMap = nullptr; michael@0: } michael@0: michael@0: if (mDyingWrappedNativeProtoMap) { michael@0: delete mDyingWrappedNativeProtoMap; michael@0: mDyingWrappedNativeProtoMap = nullptr; michael@0: } michael@0: michael@0: if (mDetachedWrappedNativeProtoMap) { michael@0: delete mDetachedWrappedNativeProtoMap; michael@0: mDetachedWrappedNativeProtoMap = nullptr; michael@0: } michael@0: michael@0: #ifdef MOZ_ENABLE_PROFILER_SPS michael@0: // Tell the profiler that the runtime is gone michael@0: if (PseudoStack *stack = mozilla_get_pseudo_stack()) michael@0: stack->sampleRuntime(nullptr); michael@0: #endif michael@0: michael@0: #ifdef DEBUG michael@0: for (uint32_t i = 0; i < XPCCCX_STRING_CACHE_SIZE; ++i) { michael@0: MOZ_ASSERT(mScratchStrings[i].empty(), "Short lived string still in use"); michael@0: } michael@0: #endif michael@0: michael@0: Preferences::UnregisterCallback(ReloadPrefsCallback, JS_OPTIONS_DOT_STR, this); michael@0: } michael@0: michael@0: static void michael@0: GetCompartmentName(JSCompartment *c, nsCString &name, bool replaceSlashes) michael@0: { michael@0: if (js::IsAtomsCompartment(c)) { michael@0: name.AssignLiteral("atoms"); michael@0: } else if (JSPrincipals *principals = JS_GetCompartmentPrincipals(c)) { michael@0: nsJSPrincipals::get(principals)->GetScriptLocation(name); michael@0: michael@0: // If the compartment's location (name) differs from the principal's michael@0: // script location, append the compartment's location to allow michael@0: // differentiation of multiple compartments owned by the same principal michael@0: // (e.g. components owned by the system or null principal). michael@0: CompartmentPrivate *compartmentPrivate = GetCompartmentPrivate(c); michael@0: if (compartmentPrivate) { michael@0: const nsACString& location = compartmentPrivate->GetLocation(); michael@0: if (!location.IsEmpty() && !location.Equals(name)) { michael@0: name.AppendLiteral(", "); michael@0: name.Append(location); michael@0: } michael@0: } michael@0: michael@0: // A hack: replace forward slashes with '\\' so they aren't michael@0: // treated as path separators. Users of the reporters michael@0: // (such as about:memory) have to undo this change. michael@0: if (replaceSlashes) michael@0: name.ReplaceChar('/', '\\'); michael@0: } else { michael@0: name.AssignLiteral("null-principal"); michael@0: } michael@0: } michael@0: michael@0: static int64_t michael@0: JSMainRuntimeGCHeapDistinguishedAmount() michael@0: { michael@0: JSRuntime *rt = nsXPConnect::GetRuntimeInstance()->Runtime(); michael@0: return int64_t(JS_GetGCParameter(rt, JSGC_TOTAL_CHUNKS)) * michael@0: js::gc::ChunkSize; michael@0: } michael@0: michael@0: static int64_t michael@0: JSMainRuntimeTemporaryPeakDistinguishedAmount() michael@0: { michael@0: JSRuntime *rt = nsXPConnect::GetRuntimeInstance()->Runtime(); michael@0: return JS::PeakSizeOfTemporary(rt); michael@0: } michael@0: michael@0: static int64_t michael@0: JSMainRuntimeCompartmentsSystemDistinguishedAmount() michael@0: { michael@0: JSRuntime *rt = nsXPConnect::GetRuntimeInstance()->Runtime(); michael@0: return JS::SystemCompartmentCount(rt); michael@0: } michael@0: michael@0: static int64_t michael@0: JSMainRuntimeCompartmentsUserDistinguishedAmount() michael@0: { michael@0: JSRuntime *rt = nsXPConnect::GetRuntimeInstance()->Runtime(); michael@0: return JS::UserCompartmentCount(rt); michael@0: } michael@0: michael@0: class JSMainRuntimeTemporaryPeakReporter MOZ_FINAL : public nsIMemoryReporter michael@0: { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: michael@0: NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, michael@0: nsISupports* aData) michael@0: { michael@0: return MOZ_COLLECT_REPORT("js-main-runtime-temporary-peak", michael@0: KIND_OTHER, UNITS_BYTES, michael@0: JSMainRuntimeTemporaryPeakDistinguishedAmount(), michael@0: "Peak transient data size in the main JSRuntime (the current size " michael@0: "of which is reported as " michael@0: "'explicit/js-non-window/runtime/temporary')."); michael@0: } michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(JSMainRuntimeTemporaryPeakReporter, nsIMemoryReporter) michael@0: michael@0: // The REPORT* macros do an unconditional report. The ZCREPORT* macros are for michael@0: // compartments and zones; they aggregate any entries smaller than michael@0: // SUNDRIES_THRESHOLD into the "sundries/gc-heap" and "sundries/malloc-heap" michael@0: // entries for the compartment. michael@0: michael@0: #define SUNDRIES_THRESHOLD js::MemoryReportingSundriesThreshold() michael@0: michael@0: #define REPORT(_path, _kind, _units, _amount, _desc) \ michael@0: do { \ michael@0: nsresult rv; \ michael@0: rv = cb->Callback(EmptyCString(), _path, \ michael@0: nsIMemoryReporter::_kind, \ michael@0: nsIMemoryReporter::_units, \ michael@0: _amount, \ michael@0: NS_LITERAL_CSTRING(_desc), \ michael@0: closure); \ michael@0: NS_ENSURE_SUCCESS(rv, rv); \ michael@0: } while (0) michael@0: michael@0: #define REPORT_BYTES(_path, _kind, _amount, _desc) \ michael@0: REPORT(_path, _kind, UNITS_BYTES, _amount, _desc); michael@0: michael@0: #define REPORT_GC_BYTES(_path, _amount, _desc) \ michael@0: do { \ michael@0: size_t amount = _amount; /* evaluate _amount only once */ \ michael@0: nsresult rv; \ michael@0: rv = cb->Callback(EmptyCString(), _path, \ michael@0: nsIMemoryReporter::KIND_NONHEAP, \ michael@0: nsIMemoryReporter::UNITS_BYTES, amount, \ michael@0: NS_LITERAL_CSTRING(_desc), closure); \ michael@0: NS_ENSURE_SUCCESS(rv, rv); \ michael@0: gcTotal += amount; \ michael@0: } while (0) michael@0: michael@0: // Report compartment/zone non-GC (KIND_HEAP) bytes. michael@0: #define ZCREPORT_BYTES(_path, _amount, _desc) \ michael@0: do { \ michael@0: /* Assign _descLiteral plus "" into a char* to prove that it's */ \ michael@0: /* actually a literal. */ \ michael@0: size_t amount = _amount; /* evaluate _amount only once */ \ michael@0: if (amount >= SUNDRIES_THRESHOLD) { \ michael@0: nsresult rv; \ michael@0: rv = cb->Callback(EmptyCString(), _path, \ michael@0: nsIMemoryReporter::KIND_HEAP, \ michael@0: nsIMemoryReporter::UNITS_BYTES, amount, \ michael@0: NS_LITERAL_CSTRING(_desc), closure); \ michael@0: NS_ENSURE_SUCCESS(rv, rv); \ michael@0: } else { \ michael@0: sundriesMallocHeap += amount; \ michael@0: } \ michael@0: } while (0) michael@0: michael@0: // Report compartment/zone GC bytes. michael@0: #define ZCREPORT_GC_BYTES(_path, _amount, _desc) \ michael@0: do { \ michael@0: size_t amount = _amount; /* evaluate _amount only once */ \ michael@0: if (amount >= SUNDRIES_THRESHOLD) { \ michael@0: nsresult rv; \ michael@0: rv = cb->Callback(EmptyCString(), _path, \ michael@0: nsIMemoryReporter::KIND_NONHEAP, \ michael@0: nsIMemoryReporter::UNITS_BYTES, amount, \ michael@0: NS_LITERAL_CSTRING(_desc), closure); \ michael@0: NS_ENSURE_SUCCESS(rv, rv); \ michael@0: gcTotal += amount; \ michael@0: } else { \ michael@0: sundriesGCHeap += amount; \ michael@0: } \ michael@0: } while (0) michael@0: michael@0: // Report runtime bytes. michael@0: #define RREPORT_BYTES(_path, _kind, _amount, _desc) \ michael@0: do { \ michael@0: size_t amount = _amount; /* evaluate _amount only once */ \ michael@0: nsresult rv; \ michael@0: rv = cb->Callback(EmptyCString(), _path, \ michael@0: nsIMemoryReporter::_kind, \ michael@0: nsIMemoryReporter::UNITS_BYTES, amount, \ michael@0: NS_LITERAL_CSTRING(_desc), closure); \ michael@0: NS_ENSURE_SUCCESS(rv, rv); \ michael@0: rtTotal += amount; \ michael@0: } while (0) michael@0: michael@0: MOZ_DEFINE_MALLOC_SIZE_OF(JSMallocSizeOf) michael@0: michael@0: namespace xpc { michael@0: michael@0: static nsresult michael@0: ReportZoneStats(const JS::ZoneStats &zStats, michael@0: const xpc::ZoneStatsExtras &extras, michael@0: nsIMemoryReporterCallback *cb, michael@0: nsISupports *closure, size_t *gcTotalOut = nullptr) michael@0: { michael@0: const nsAutoCString& pathPrefix = extras.pathPrefix; michael@0: size_t gcTotal = 0, sundriesGCHeap = 0, sundriesMallocHeap = 0; michael@0: michael@0: MOZ_ASSERT(!gcTotalOut == zStats.isTotals); michael@0: michael@0: ZCREPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("gc-heap-arena-admin"), michael@0: zStats.gcHeapArenaAdmin, michael@0: "Bookkeeping information and alignment padding within GC arenas."); michael@0: michael@0: ZCREPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("unused-gc-things"), michael@0: zStats.unusedGCThings, michael@0: "Empty GC thing cells within non-empty arenas."); michael@0: michael@0: ZCREPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("lazy-scripts/gc-heap"), michael@0: zStats.lazyScriptsGCHeap, michael@0: "Scripts that haven't executed yet."); michael@0: michael@0: ZCREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("lazy-scripts/malloc-heap"), michael@0: zStats.lazyScriptsMallocHeap, michael@0: "Lazy script tables containing free variables or inner functions."); michael@0: michael@0: ZCREPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("jit-codes-gc-heap"), michael@0: zStats.jitCodesGCHeap, michael@0: "References to executable code pools used by the JITs."); michael@0: michael@0: ZCREPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("type-objects/gc-heap"), michael@0: zStats.typeObjectsGCHeap, michael@0: "Type inference information about objects."); michael@0: michael@0: ZCREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("type-objects/malloc-heap"), michael@0: zStats.typeObjectsMallocHeap, michael@0: "Type object addenda."); michael@0: michael@0: ZCREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("type-pool"), michael@0: zStats.typePool, michael@0: "Type sets and related data."); michael@0: michael@0: ZCREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("baseline/optimized-stubs"), michael@0: zStats.baselineStubsOptimized, michael@0: "The Baseline JIT's optimized IC stubs (excluding code)."); michael@0: michael@0: size_t stringsNotableAboutMemoryGCHeap = 0; michael@0: size_t stringsNotableAboutMemoryMallocHeap = 0; michael@0: michael@0: #define MAYBE_INLINE \ michael@0: "The characters may be inline or on the malloc heap." michael@0: #define MAYBE_OVERALLOCATED \ michael@0: "Sometimes over-allocated to simplify string concatenation." michael@0: michael@0: for (size_t i = 0; i < zStats.notableStrings.length(); i++) { michael@0: const JS::NotableStringInfo& info = zStats.notableStrings[i]; michael@0: michael@0: MOZ_ASSERT(!zStats.isTotals); michael@0: michael@0: nsDependentCString notableString(info.buffer); michael@0: michael@0: // Viewing about:memory generates many notable strings which contain michael@0: // "string(length=". If we report these as notable, then we'll create michael@0: // even more notable strings the next time we open about:memory (unless michael@0: // there's a GC in the meantime), and so on ad infinitum. michael@0: // michael@0: // To avoid cluttering up about:memory like this, we stick notable michael@0: // strings which contain "string(length=" into their own bucket. michael@0: # define STRING_LENGTH "string(length=" michael@0: if (FindInReadable(NS_LITERAL_CSTRING(STRING_LENGTH), notableString)) { michael@0: stringsNotableAboutMemoryGCHeap += info.gcHeap; michael@0: stringsNotableAboutMemoryMallocHeap += info.mallocHeap; michael@0: continue; michael@0: } michael@0: michael@0: // Escape / to \ before we put notableString into the memory reporter michael@0: // path, because we don't want any forward slashes in the string to michael@0: // count as path separators. michael@0: nsCString escapedString(notableString); michael@0: escapedString.ReplaceSubstring("/", "\\"); michael@0: michael@0: bool truncated = notableString.Length() < info.length; michael@0: michael@0: nsCString path = pathPrefix + michael@0: nsPrintfCString("strings/" STRING_LENGTH "%d, copies=%d, \"%s\"%s)/", michael@0: info.length, info.numCopies, escapedString.get(), michael@0: truncated ? " (truncated)" : ""); michael@0: michael@0: if (info.gcHeap > 0) { michael@0: REPORT_GC_BYTES(path + NS_LITERAL_CSTRING("gc-heap"), michael@0: info.gcHeap, michael@0: "Strings. " MAYBE_INLINE); michael@0: } michael@0: michael@0: if (info.mallocHeap > 0) { michael@0: REPORT_BYTES(path + NS_LITERAL_CSTRING("malloc-heap"), michael@0: KIND_HEAP, info.mallocHeap, michael@0: "Non-inline string characters. " MAYBE_OVERALLOCATED); michael@0: } michael@0: } michael@0: michael@0: nsCString nonNotablePath = pathPrefix; michael@0: nonNotablePath += zStats.isTotals michael@0: ? NS_LITERAL_CSTRING("strings/") michael@0: : NS_LITERAL_CSTRING("strings/string()/"); michael@0: michael@0: if (zStats.stringInfo.gcHeap > 0) { michael@0: REPORT_GC_BYTES(nonNotablePath + NS_LITERAL_CSTRING("gc-heap"), michael@0: zStats.stringInfo.gcHeap, michael@0: "Strings. " MAYBE_INLINE); michael@0: } michael@0: michael@0: if (zStats.stringInfo.mallocHeap > 0) { michael@0: REPORT_BYTES(nonNotablePath + NS_LITERAL_CSTRING("malloc-heap"), michael@0: KIND_HEAP, zStats.stringInfo.mallocHeap, michael@0: "Non-inline string characters. " MAYBE_OVERALLOCATED); michael@0: } michael@0: michael@0: if (stringsNotableAboutMemoryGCHeap > 0) { michael@0: MOZ_ASSERT(!zStats.isTotals); michael@0: REPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("strings/string()/gc-heap"), michael@0: stringsNotableAboutMemoryGCHeap, michael@0: "Strings that contain the characters '" STRING_LENGTH "', which " michael@0: "are probably from about:memory itself." MAYBE_INLINE michael@0: " We filter them out rather than display them, because displaying " michael@0: "them would create even more such strings every time about:memory " michael@0: "is refreshed."); michael@0: } michael@0: michael@0: if (stringsNotableAboutMemoryMallocHeap > 0) { michael@0: MOZ_ASSERT(!zStats.isTotals); michael@0: REPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("strings/string()/malloc-heap"), michael@0: KIND_HEAP, stringsNotableAboutMemoryMallocHeap, michael@0: "Non-inline string characters of strings that contain the " michael@0: "characters '" STRING_LENGTH "', which are probably from " michael@0: "about:memory itself. " MAYBE_OVERALLOCATED michael@0: " We filter them out rather than display them, because displaying " michael@0: "them would create even more such strings every time about:memory " michael@0: "is refreshed."); michael@0: } michael@0: michael@0: if (sundriesGCHeap > 0) { michael@0: // We deliberately don't use ZCREPORT_GC_BYTES here. michael@0: REPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("sundries/gc-heap"), michael@0: sundriesGCHeap, michael@0: "The sum of all 'gc-heap' measurements that are too small to be " michael@0: "worth showing individually."); michael@0: } michael@0: michael@0: if (sundriesMallocHeap > 0) { michael@0: // We deliberately don't use ZCREPORT_BYTES here. michael@0: REPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("sundries/malloc-heap"), michael@0: KIND_HEAP, sundriesMallocHeap, michael@0: "The sum of all 'malloc-heap' measurements that are too small to " michael@0: "be worth showing individually."); michael@0: } michael@0: michael@0: if (gcTotalOut) michael@0: *gcTotalOut += gcTotal; michael@0: michael@0: return NS_OK; michael@0: michael@0: # undef STRING_LENGTH michael@0: } michael@0: michael@0: static nsresult michael@0: ReportCompartmentStats(const JS::CompartmentStats &cStats, michael@0: const xpc::CompartmentStatsExtras &extras, michael@0: amIAddonManager *addonManager, michael@0: nsIMemoryReporterCallback *cb, michael@0: nsISupports *closure, size_t *gcTotalOut = nullptr) michael@0: { michael@0: static const nsDependentCString addonPrefix("explicit/add-ons/"); michael@0: michael@0: size_t gcTotal = 0, sundriesGCHeap = 0, sundriesMallocHeap = 0; michael@0: nsAutoCString cJSPathPrefix = extras.jsPathPrefix; michael@0: nsAutoCString cDOMPathPrefix = extras.domPathPrefix; michael@0: michael@0: // Only attempt to prefix if we got a location and the path wasn't already michael@0: // prefixed. michael@0: if (extras.location && addonManager && michael@0: cJSPathPrefix.Find(addonPrefix, false, 0, 0) != 0) { michael@0: nsAutoCString addonId; michael@0: bool ok; michael@0: if (NS_SUCCEEDED(addonManager->MapURIToAddonID(extras.location, michael@0: addonId, &ok)) michael@0: && ok) { michael@0: // Insert the add-on id as "add-ons/@id@/" after "explicit/" to michael@0: // aggregate add-on compartments. michael@0: static const size_t explicitLength = strlen("explicit/"); michael@0: addonId.Insert(NS_LITERAL_CSTRING("add-ons/"), 0); michael@0: addonId += "/"; michael@0: cJSPathPrefix.Insert(addonId, explicitLength); michael@0: cDOMPathPrefix.Insert(addonId, explicitLength); michael@0: } michael@0: } michael@0: michael@0: ZCREPORT_GC_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("objects/gc-heap/ordinary"), michael@0: cStats.objectsGCHeapOrdinary, michael@0: "Ordinary objects, i.e. not otherwise distinguished by memory " michael@0: "reporters."); michael@0: michael@0: ZCREPORT_GC_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("objects/gc-heap/function"), michael@0: cStats.objectsGCHeapFunction, michael@0: "Function objects."); michael@0: michael@0: ZCREPORT_GC_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("objects/gc-heap/dense-array"), michael@0: cStats.objectsGCHeapDenseArray, michael@0: "Dense array objects."); michael@0: michael@0: ZCREPORT_GC_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("objects/gc-heap/slow-array"), michael@0: cStats.objectsGCHeapSlowArray, michael@0: "Slow array objects."); michael@0: michael@0: ZCREPORT_GC_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("objects/gc-heap/cross-compartment-wrapper"), michael@0: cStats.objectsGCHeapCrossCompartmentWrapper, michael@0: "Cross-compartment wrapper objects."); michael@0: michael@0: // Note that we use cDOMPathPrefix here. This is because we measure orphan michael@0: // DOM nodes in the JS reporter, but we want to report them in a "dom" michael@0: // sub-tree rather than a "js" sub-tree. michael@0: ZCREPORT_BYTES(cDOMPathPrefix + NS_LITERAL_CSTRING("orphan-nodes"), michael@0: cStats.objectsPrivate, michael@0: "Orphan DOM nodes, i.e. those that are only reachable from JavaScript " michael@0: "objects."); michael@0: michael@0: ZCREPORT_GC_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("shapes/gc-heap/tree/global-parented"), michael@0: cStats.shapesGCHeapTreeGlobalParented, michael@0: "Shapes that (a) are in a property tree, and (b) represent an object " michael@0: "whose parent is the global object."); michael@0: michael@0: ZCREPORT_GC_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("shapes/gc-heap/tree/non-global-parented"), michael@0: cStats.shapesGCHeapTreeNonGlobalParented, michael@0: "Shapes that (a) are in a property tree, and (b) represent an object " michael@0: "whose parent is not the global object."); michael@0: michael@0: ZCREPORT_GC_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("shapes/gc-heap/dict"), michael@0: cStats.shapesGCHeapDict, michael@0: "Shapes that are in dictionary mode."); michael@0: michael@0: ZCREPORT_GC_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("shapes/gc-heap/base"), michael@0: cStats.shapesGCHeapBase, michael@0: "Base shapes, which collate data common to many shapes."); michael@0: michael@0: ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("shapes/malloc-heap/tree-tables"), michael@0: cStats.shapesMallocHeapTreeTables, michael@0: "Property tables belonging to shapes that are in a property tree."); michael@0: michael@0: ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("shapes/malloc-heap/dict-tables"), michael@0: cStats.shapesMallocHeapDictTables, michael@0: "Property tables that belong to shapes that are in dictionary mode."); michael@0: michael@0: ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("shapes/malloc-heap/tree-shape-kids"), michael@0: cStats.shapesMallocHeapTreeShapeKids, michael@0: "Kid hashes that belong to shapes that are in a property tree."); michael@0: michael@0: ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("shapes/malloc-heap/compartment-tables"), michael@0: cStats.shapesMallocHeapCompartmentTables, michael@0: "Compartment-wide tables storing shape information used during object " michael@0: "construction."); michael@0: michael@0: ZCREPORT_GC_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("scripts/gc-heap"), michael@0: cStats.scriptsGCHeap, michael@0: "JSScript instances. There is one per user-defined function in a " michael@0: "script, and one for the top-level code in a script."); michael@0: michael@0: ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("scripts/malloc-heap/data"), michael@0: cStats.scriptsMallocHeapData, michael@0: "Various variable-length tables in JSScripts."); michael@0: michael@0: ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("baseline/data"), michael@0: cStats.baselineData, michael@0: "The Baseline JIT's compilation data (BaselineScripts)."); michael@0: michael@0: ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("baseline/fallback-stubs"), michael@0: cStats.baselineStubsFallback, michael@0: "The Baseline JIT's fallback IC stubs (excluding code)."); michael@0: michael@0: ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("ion-data"), michael@0: cStats.ionData, michael@0: "The IonMonkey JIT's compilation data (IonScripts)."); michael@0: michael@0: ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("type-inference/type-scripts"), michael@0: cStats.typeInferenceTypeScripts, michael@0: "Type sets associated with scripts."); michael@0: michael@0: ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("type-inference/allocation-site-tables"), michael@0: cStats.typeInferenceAllocationSiteTables, michael@0: "Tables of type objects associated with allocation sites."); michael@0: michael@0: ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("type-inference/array-type-tables"), michael@0: cStats.typeInferenceArrayTypeTables, michael@0: "Tables of type objects associated with array literals."); michael@0: michael@0: ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("type-inference/object-type-tables"), michael@0: cStats.typeInferenceObjectTypeTables, michael@0: "Tables of type objects associated with object literals."); michael@0: michael@0: ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("compartment-object"), michael@0: cStats.compartmentObject, michael@0: "The JSCompartment object itself."); michael@0: michael@0: ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("cross-compartment-wrapper-table"), michael@0: cStats.crossCompartmentWrappersTable, michael@0: "The cross-compartment wrapper table."); michael@0: michael@0: ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("regexp-compartment"), michael@0: cStats.regexpCompartment, michael@0: "The regexp compartment."); michael@0: michael@0: ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("debuggees-set"), michael@0: cStats.debuggeesSet, michael@0: "The debuggees set."); michael@0: michael@0: ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("objects/malloc-heap/slots"), michael@0: cStats.objectsExtra.mallocHeapSlots, michael@0: "Non-fixed object slot arrays, which represent object properties."); michael@0: michael@0: ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("objects/malloc-heap/elements/non-asm.js"), michael@0: cStats.objectsExtra.mallocHeapElementsNonAsmJS, michael@0: "Non-asm.js indexed elements."); michael@0: michael@0: // asm.js arrays are heap-allocated on some platforms and michael@0: // non-heap-allocated on others. We never put them under sundries, michael@0: // because (a) in practice they're almost always larger than the sundries michael@0: // threshold, and (b) we'd need a third category of sundries ("non-heap"), michael@0: // which would be a pain. michael@0: size_t mallocHeapElementsAsmJS = cStats.objectsExtra.mallocHeapElementsAsmJS; michael@0: size_t nonHeapElementsAsmJS = cStats.objectsExtra.nonHeapElementsAsmJS; michael@0: MOZ_ASSERT(mallocHeapElementsAsmJS == 0 || nonHeapElementsAsmJS == 0); michael@0: if (mallocHeapElementsAsmJS > 0) { michael@0: REPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("objects/malloc-heap/elements/asm.js"), michael@0: KIND_HEAP, mallocHeapElementsAsmJS, michael@0: "asm.js array buffer elements on the malloc heap."); michael@0: } michael@0: if (nonHeapElementsAsmJS > 0) { michael@0: REPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("objects/non-heap/elements/asm.js"), michael@0: KIND_NONHEAP, nonHeapElementsAsmJS, michael@0: "asm.js array buffer elements outside both the malloc heap and " michael@0: "the GC heap."); michael@0: } michael@0: michael@0: if (cStats.objectsExtra.nonHeapElementsMapped > 0) { michael@0: REPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("objects/non-heap/elements/mapped"), michael@0: KIND_NONHEAP, cStats.objectsExtra.nonHeapElementsMapped, michael@0: "Memory-mapped array buffer elements."); michael@0: } michael@0: michael@0: REPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("objects/non-heap/code/asm.js"), michael@0: KIND_NONHEAP, cStats.objectsExtra.nonHeapCodeAsmJS, michael@0: "AOT-compiled asm.js code."); michael@0: michael@0: ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("objects/malloc-heap/asm.js-module-data"), michael@0: cStats.objectsExtra.mallocHeapAsmJSModuleData, michael@0: "asm.js module data."); michael@0: michael@0: ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("objects/malloc-heap/arguments-data"), michael@0: cStats.objectsExtra.mallocHeapArgumentsData, michael@0: "Data belonging to Arguments objects."); michael@0: michael@0: ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("objects/malloc-heap/regexp-statics"), michael@0: cStats.objectsExtra.mallocHeapRegExpStatics, michael@0: "Data belonging to the RegExpStatics object."); michael@0: michael@0: ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("objects/malloc-heap/property-iterator-data"), michael@0: cStats.objectsExtra.mallocHeapPropertyIteratorData, michael@0: "Data belonging to property iterator objects."); michael@0: michael@0: ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("objects/malloc-heap/ctypes-data"), michael@0: cStats.objectsExtra.mallocHeapCtypesData, michael@0: "Data belonging to ctypes objects."); michael@0: michael@0: if (sundriesGCHeap > 0) { michael@0: // We deliberately don't use ZCREPORT_GC_BYTES here. michael@0: REPORT_GC_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("sundries/gc-heap"), michael@0: sundriesGCHeap, michael@0: "The sum of all 'gc-heap' measurements that are too small to be " michael@0: "worth showing individually."); michael@0: } michael@0: michael@0: if (sundriesMallocHeap > 0) { michael@0: // We deliberately don't use ZCREPORT_BYTES here. michael@0: REPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("sundries/malloc-heap"), michael@0: KIND_HEAP, sundriesMallocHeap, michael@0: "The sum of all 'malloc-heap' measurements that are too small to " michael@0: "be worth showing individually."); michael@0: } michael@0: michael@0: if (gcTotalOut) michael@0: *gcTotalOut += gcTotal; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: static nsresult michael@0: ReportScriptSourceStats(const ScriptSourceInfo &scriptSourceInfo, michael@0: const nsACString &path, michael@0: nsIHandleReportCallback *cb, nsISupports *closure, michael@0: size_t &rtTotal) michael@0: { michael@0: if (scriptSourceInfo.compressed > 0) { michael@0: RREPORT_BYTES(path + NS_LITERAL_CSTRING("compressed"), michael@0: KIND_HEAP, scriptSourceInfo.compressed, michael@0: "Compressed JavaScript source code."); michael@0: } michael@0: michael@0: if (scriptSourceInfo.uncompressed > 0) { michael@0: RREPORT_BYTES(path + NS_LITERAL_CSTRING("uncompressed"), michael@0: KIND_HEAP, scriptSourceInfo.uncompressed, michael@0: "Uncompressed JavaScript source code."); michael@0: } michael@0: michael@0: if (scriptSourceInfo.misc > 0) { michael@0: RREPORT_BYTES(path + NS_LITERAL_CSTRING("misc"), michael@0: KIND_HEAP, scriptSourceInfo.misc, michael@0: "Miscellaneous data relating to JavaScript source code."); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: static nsresult michael@0: ReportJSRuntimeExplicitTreeStats(const JS::RuntimeStats &rtStats, michael@0: const nsACString &rtPath, michael@0: amIAddonManager* addonManager, michael@0: nsIMemoryReporterCallback *cb, michael@0: nsISupports *closure, size_t *rtTotalOut) michael@0: { michael@0: nsresult rv; michael@0: michael@0: size_t gcTotal = 0; michael@0: michael@0: for (size_t i = 0; i < rtStats.zoneStatsVector.length(); i++) { michael@0: const JS::ZoneStats &zStats = rtStats.zoneStatsVector[i]; michael@0: const xpc::ZoneStatsExtras *extras = michael@0: static_cast(zStats.extra); michael@0: rv = ReportZoneStats(zStats, *extras, cb, closure, &gcTotal); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: for (size_t i = 0; i < rtStats.compartmentStatsVector.length(); i++) { michael@0: JS::CompartmentStats cStats = rtStats.compartmentStatsVector[i]; michael@0: const xpc::CompartmentStatsExtras *extras = michael@0: static_cast(cStats.extra); michael@0: rv = ReportCompartmentStats(cStats, *extras, addonManager, cb, closure, michael@0: &gcTotal); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: // Report the rtStats.runtime numbers under "runtime/", and compute their michael@0: // total for later. michael@0: michael@0: size_t rtTotal = 0; michael@0: michael@0: RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/runtime-object"), michael@0: KIND_HEAP, rtStats.runtime.object, michael@0: "The JSRuntime object."); michael@0: michael@0: RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/atoms-table"), michael@0: KIND_HEAP, rtStats.runtime.atomsTable, michael@0: "The atoms table."); michael@0: michael@0: RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/contexts"), michael@0: KIND_HEAP, rtStats.runtime.contexts, michael@0: "JSContext objects and structures that belong to them."); michael@0: michael@0: RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/dtoa"), michael@0: KIND_HEAP, rtStats.runtime.dtoa, michael@0: "The DtoaState object, which is used for converting strings to " michael@0: "numbers and vice versa."); michael@0: michael@0: RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/temporary"), michael@0: KIND_HEAP, rtStats.runtime.temporary, michael@0: "Transient data (mostly parse nodes) held by the JSRuntime during " michael@0: "compilation."); michael@0: michael@0: RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/regexp-data"), michael@0: KIND_NONHEAP, rtStats.runtime.regexpData, michael@0: "Regexp JIT data."); michael@0: michael@0: RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/interpreter-stack"), michael@0: KIND_HEAP, rtStats.runtime.interpreterStack, michael@0: "JS interpreter frames."); michael@0: michael@0: RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/math-cache"), michael@0: KIND_HEAP, rtStats.runtime.mathCache, michael@0: "The math cache."); michael@0: michael@0: RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/source-data-cache"), michael@0: KIND_HEAP, rtStats.runtime.sourceDataCache, michael@0: "The source data cache, which holds decompressed script source code."); michael@0: michael@0: RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/script-data"), michael@0: KIND_HEAP, rtStats.runtime.scriptData, michael@0: "The table holding script data shared in the runtime."); michael@0: michael@0: nsCString nonNotablePath = michael@0: rtPath + nsPrintfCString("runtime/script-sources/source(scripts=%d, )/", michael@0: rtStats.runtime.scriptSourceInfo.numScripts); michael@0: michael@0: rv = ReportScriptSourceStats(rtStats.runtime.scriptSourceInfo, michael@0: nonNotablePath, cb, closure, rtTotal); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: for (size_t i = 0; i < rtStats.runtime.notableScriptSources.length(); i++) { michael@0: const JS::NotableScriptSourceInfo& scriptSourceInfo = michael@0: rtStats.runtime.notableScriptSources[i]; michael@0: michael@0: // Escape / to \ before we put the filename into the memory reporter michael@0: // path, because we don't want any forward slashes in the string to michael@0: // count as path separators. Consumers of memory reporters (e.g. michael@0: // about:memory) will convert them back to / after doing path michael@0: // splitting. michael@0: nsDependentCString filename(scriptSourceInfo.filename_); michael@0: nsCString escapedFilename(filename); michael@0: escapedFilename.ReplaceSubstring("/", "\\"); michael@0: michael@0: nsCString notablePath = rtPath + michael@0: nsPrintfCString("runtime/script-sources/source(scripts=%d, %s)/", michael@0: scriptSourceInfo.numScripts, escapedFilename.get()); michael@0: michael@0: rv = ReportScriptSourceStats(scriptSourceInfo, notablePath, michael@0: cb, closure, rtTotal); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/code/ion"), michael@0: KIND_NONHEAP, rtStats.runtime.code.ion, michael@0: "Code generated by the IonMonkey JIT."); michael@0: michael@0: RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/code/baseline"), michael@0: KIND_NONHEAP, rtStats.runtime.code.baseline, michael@0: "Code generated by the Baseline JIT."); michael@0: michael@0: RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/code/regexp"), michael@0: KIND_NONHEAP, rtStats.runtime.code.regexp, michael@0: "Code generated by the regexp JIT."); michael@0: michael@0: RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/code/other"), michael@0: KIND_NONHEAP, rtStats.runtime.code.other, michael@0: "Code generated by the JITs for wrappers and trampolines."); michael@0: michael@0: RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/code/unused"), michael@0: KIND_NONHEAP, rtStats.runtime.code.unused, michael@0: "Memory allocated by one of the JITs to hold code, but which is " michael@0: "currently unused."); michael@0: michael@0: RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/gc/marker"), michael@0: KIND_HEAP, rtStats.runtime.gc.marker, michael@0: "The GC mark stack and gray roots."); michael@0: michael@0: RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/gc/nursery-committed"), michael@0: KIND_NONHEAP, rtStats.runtime.gc.nurseryCommitted, michael@0: "Memory being used by the GC's nursery."); michael@0: michael@0: RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/gc/nursery-huge-slots"), michael@0: KIND_NONHEAP, rtStats.runtime.gc.nurseryHugeSlots, michael@0: "Out-of-line slots and elements belonging to objects in the " michael@0: "nursery."); michael@0: michael@0: RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/gc/store-buffer/vals"), michael@0: KIND_HEAP, rtStats.runtime.gc.storeBufferVals, michael@0: "Values in the store buffer."); michael@0: michael@0: RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/gc/store-buffer/cells"), michael@0: KIND_HEAP, rtStats.runtime.gc.storeBufferCells, michael@0: "Cells in the store buffer."); michael@0: michael@0: RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/gc/store-buffer/slots"), michael@0: KIND_HEAP, rtStats.runtime.gc.storeBufferSlots, michael@0: "Slots in the store buffer."); michael@0: michael@0: RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/gc/store-buffer/whole-cells"), michael@0: KIND_HEAP, rtStats.runtime.gc.storeBufferWholeCells, michael@0: "Whole cells in the store buffer."); michael@0: michael@0: RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/gc/store-buffer/reloc-vals"), michael@0: KIND_HEAP, rtStats.runtime.gc.storeBufferRelocVals, michael@0: "Relocatable values in the store buffer."); michael@0: michael@0: RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/gc/store-buffer/reloc-cells"), michael@0: KIND_HEAP, rtStats.runtime.gc.storeBufferRelocCells, michael@0: "Relocatable cells in the store buffer."); michael@0: michael@0: RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/gc/store-buffer/generics"), michael@0: KIND_HEAP, rtStats.runtime.gc.storeBufferGenerics, michael@0: "Generic things in the store buffer."); michael@0: michael@0: if (rtTotalOut) michael@0: *rtTotalOut = rtTotal; michael@0: michael@0: // Report GC numbers that don't belong to a compartment. michael@0: michael@0: // We don't want to report decommitted memory in "explicit", so we just michael@0: // change the leading "explicit/" to "decommitted/". michael@0: nsCString rtPath2(rtPath); michael@0: rtPath2.Replace(0, strlen("explicit"), NS_LITERAL_CSTRING("decommitted")); michael@0: REPORT_GC_BYTES(rtPath2 + NS_LITERAL_CSTRING("gc-heap/decommitted-arenas"), michael@0: rtStats.gcHeapDecommittedArenas, michael@0: "GC arenas in non-empty chunks that is decommitted, i.e. it takes up " michael@0: "address space but no physical memory or swap space."); michael@0: michael@0: REPORT_BYTES(rtPath2 + NS_LITERAL_CSTRING("runtime/gc/nursery-decommitted"), michael@0: KIND_NONHEAP, rtStats.runtime.gc.nurseryDecommitted, michael@0: "Memory allocated to the GC's nursery this is decommitted, i.e. it takes up " michael@0: "address space but no physical memory or swap space."); michael@0: michael@0: REPORT_GC_BYTES(rtPath + NS_LITERAL_CSTRING("gc-heap/unused-chunks"), michael@0: rtStats.gcHeapUnusedChunks, michael@0: "Empty GC chunks which will soon be released unless claimed for new " michael@0: "allocations."); michael@0: michael@0: REPORT_GC_BYTES(rtPath + NS_LITERAL_CSTRING("gc-heap/unused-arenas"), michael@0: rtStats.gcHeapUnusedArenas, michael@0: "Empty GC arenas within non-empty chunks."); michael@0: michael@0: REPORT_GC_BYTES(rtPath + NS_LITERAL_CSTRING("gc-heap/chunk-admin"), michael@0: rtStats.gcHeapChunkAdmin, michael@0: "Bookkeeping information within GC chunks."); michael@0: michael@0: // gcTotal is the sum of everything we've reported for the GC heap. It michael@0: // should equal rtStats.gcHeapChunkTotal. michael@0: MOZ_ASSERT(gcTotal == rtStats.gcHeapChunkTotal); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: ReportJSRuntimeExplicitTreeStats(const JS::RuntimeStats &rtStats, michael@0: const nsACString &rtPath, michael@0: nsIMemoryReporterCallback *cb, michael@0: nsISupports *closure, size_t *rtTotalOut) michael@0: { michael@0: nsCOMPtr am; michael@0: if (XRE_GetProcessType() == GeckoProcessType_Default) { michael@0: // Only try to access the service from the main process. michael@0: am = do_GetService("@mozilla.org/addons/integration;1"); michael@0: } michael@0: return ReportJSRuntimeExplicitTreeStats(rtStats, rtPath, am.get(), cb, michael@0: closure, rtTotalOut); michael@0: } michael@0: michael@0: michael@0: } // namespace xpc michael@0: michael@0: class JSMainRuntimeCompartmentsReporter MOZ_FINAL : public nsIMemoryReporter michael@0: { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: michael@0: typedef js::Vector Paths; michael@0: michael@0: static void CompartmentCallback(JSRuntime *rt, void* data, JSCompartment *c) { michael@0: // silently ignore OOM errors michael@0: Paths *paths = static_cast(data); michael@0: nsCString path; michael@0: GetCompartmentName(c, path, true); michael@0: path.Insert(js::IsSystemCompartment(c) michael@0: ? NS_LITERAL_CSTRING("js-main-runtime-compartments/system/") michael@0: : NS_LITERAL_CSTRING("js-main-runtime-compartments/user/"), michael@0: 0); michael@0: paths->append(path); michael@0: } michael@0: michael@0: NS_IMETHOD CollectReports(nsIMemoryReporterCallback *cb, michael@0: nsISupports *closure) michael@0: { michael@0: // First we collect the compartment paths. Then we report them. Doing michael@0: // the two steps interleaved is a bad idea, because calling |cb| michael@0: // from within CompartmentCallback() leads to all manner of assertions. michael@0: michael@0: // Collect. michael@0: michael@0: Paths paths; michael@0: JS_IterateCompartments(nsXPConnect::GetRuntimeInstance()->Runtime(), michael@0: &paths, CompartmentCallback); michael@0: michael@0: // Report. michael@0: for (size_t i = 0; i < paths.length(); i++) michael@0: // These ones don't need a description, hence the "". michael@0: REPORT(nsCString(paths[i]), KIND_OTHER, UNITS_COUNT, 1, michael@0: "A live compartment in the main JSRuntime."); michael@0: michael@0: return NS_OK; michael@0: } michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(JSMainRuntimeCompartmentsReporter, nsIMemoryReporter) michael@0: michael@0: MOZ_DEFINE_MALLOC_SIZE_OF(OrphanMallocSizeOf) michael@0: michael@0: namespace xpc { michael@0: michael@0: static size_t michael@0: SizeOfTreeIncludingThis(nsINode *tree) michael@0: { michael@0: size_t n = tree->SizeOfIncludingThis(OrphanMallocSizeOf); michael@0: for (nsIContent* child = tree->GetFirstChild(); child; child = child->GetNextNode(tree)) michael@0: n += child->SizeOfIncludingThis(OrphanMallocSizeOf); michael@0: michael@0: return n; michael@0: } michael@0: michael@0: class OrphanReporter : public JS::ObjectPrivateVisitor michael@0: { michael@0: public: michael@0: OrphanReporter(GetISupportsFun aGetISupports) michael@0: : JS::ObjectPrivateVisitor(aGetISupports) michael@0: { michael@0: } michael@0: michael@0: virtual size_t sizeOfIncludingThis(nsISupports *aSupports) MOZ_OVERRIDE { michael@0: size_t n = 0; michael@0: nsCOMPtr node = do_QueryInterface(aSupports); michael@0: // https://bugzilla.mozilla.org/show_bug.cgi?id=773533#c11 explains michael@0: // that we have to skip XBL elements because they violate certain michael@0: // assumptions. Yuk. michael@0: if (node && !node->IsInDoc() && michael@0: !(node->IsElement() && node->AsElement()->IsInNamespace(kNameSpaceID_XBL))) michael@0: { michael@0: // This is an orphan node. If we haven't already handled the michael@0: // sub-tree that this node belongs to, measure the sub-tree's size michael@0: // and then record its root so we don't measure it again. michael@0: nsCOMPtr orphanTree = node->SubtreeRoot(); michael@0: if (!mAlreadyMeasuredOrphanTrees.Contains(orphanTree)) { michael@0: n += SizeOfTreeIncludingThis(orphanTree); michael@0: mAlreadyMeasuredOrphanTrees.PutEntry(orphanTree); michael@0: } michael@0: } michael@0: return n; michael@0: } michael@0: michael@0: private: michael@0: nsTHashtable mAlreadyMeasuredOrphanTrees; michael@0: }; michael@0: michael@0: #ifdef DEBUG michael@0: static bool michael@0: StartsWithExplicit(nsACString& s) michael@0: { michael@0: const char* e = "explicit/"; michael@0: return Substring(s, 0, strlen(e)).Equals(e); michael@0: } michael@0: #endif michael@0: michael@0: class XPCJSRuntimeStats : public JS::RuntimeStats michael@0: { michael@0: WindowPaths *mWindowPaths; michael@0: WindowPaths *mTopWindowPaths; michael@0: bool mGetLocations; michael@0: michael@0: public: michael@0: XPCJSRuntimeStats(WindowPaths *windowPaths, WindowPaths *topWindowPaths, michael@0: bool getLocations) michael@0: : JS::RuntimeStats(JSMallocSizeOf), michael@0: mWindowPaths(windowPaths), michael@0: mTopWindowPaths(topWindowPaths), michael@0: mGetLocations(getLocations) michael@0: {} michael@0: michael@0: ~XPCJSRuntimeStats() { michael@0: for (size_t i = 0; i != compartmentStatsVector.length(); ++i) michael@0: delete static_cast(compartmentStatsVector[i].extra); michael@0: michael@0: michael@0: for (size_t i = 0; i != zoneStatsVector.length(); ++i) michael@0: delete static_cast(zoneStatsVector[i].extra); michael@0: } michael@0: michael@0: virtual void initExtraZoneStats(JS::Zone *zone, JS::ZoneStats *zStats) MOZ_OVERRIDE { michael@0: // Get the compartment's global. michael@0: nsXPConnect *xpc = nsXPConnect::XPConnect(); michael@0: AutoSafeJSContext cx; michael@0: JSCompartment *comp = js::GetAnyCompartmentInZone(zone); michael@0: xpc::ZoneStatsExtras *extras = new xpc::ZoneStatsExtras; michael@0: extras->pathPrefix.AssignLiteral("explicit/js-non-window/zones/"); michael@0: RootedObject global(cx, JS_GetGlobalForCompartmentOrNull(cx, comp)); michael@0: if (global) { michael@0: // Need to enter the compartment, otherwise GetNativeOfWrapper() michael@0: // might crash. michael@0: JSAutoCompartment ac(cx, global); michael@0: nsISupports *native = xpc->GetNativeOfWrapper(cx, global); michael@0: if (nsCOMPtr piwindow = do_QueryInterface(native)) { michael@0: // The global is a |window| object. Use the path prefix that michael@0: // we should have already created for it. michael@0: if (mTopWindowPaths->Get(piwindow->WindowID(), michael@0: &extras->pathPrefix)) michael@0: extras->pathPrefix.AppendLiteral("/js-"); michael@0: } michael@0: } michael@0: michael@0: extras->pathPrefix += nsPrintfCString("zone(0x%p)/", (void *)zone); michael@0: michael@0: MOZ_ASSERT(StartsWithExplicit(extras->pathPrefix)); michael@0: michael@0: zStats->extra = extras; michael@0: } michael@0: michael@0: virtual void initExtraCompartmentStats(JSCompartment *c, michael@0: JS::CompartmentStats *cstats) MOZ_OVERRIDE michael@0: { michael@0: xpc::CompartmentStatsExtras *extras = new xpc::CompartmentStatsExtras; michael@0: nsCString cName; michael@0: GetCompartmentName(c, cName, true); michael@0: if (mGetLocations) { michael@0: CompartmentPrivate *cp = GetCompartmentPrivate(c); michael@0: if (cp) michael@0: cp->GetLocationURI(CompartmentPrivate::LocationHintAddon, michael@0: getter_AddRefs(extras->location)); michael@0: // Note: cannot use amIAddonManager implementation at this point, michael@0: // as it is a JS service and the JS heap is currently not idle. michael@0: // Otherwise, we could have computed the add-on id at this point. michael@0: } michael@0: michael@0: // Get the compartment's global. michael@0: nsXPConnect *xpc = nsXPConnect::XPConnect(); michael@0: AutoSafeJSContext cx; michael@0: bool needZone = true; michael@0: RootedObject global(cx, JS_GetGlobalForCompartmentOrNull(cx, c)); michael@0: if (global) { michael@0: // Need to enter the compartment, otherwise GetNativeOfWrapper() michael@0: // might crash. michael@0: JSAutoCompartment ac(cx, global); michael@0: nsISupports *native = xpc->GetNativeOfWrapper(cx, global); michael@0: if (nsCOMPtr piwindow = do_QueryInterface(native)) { michael@0: // The global is a |window| object. Use the path prefix that michael@0: // we should have already created for it. michael@0: if (mWindowPaths->Get(piwindow->WindowID(), michael@0: &extras->jsPathPrefix)) { michael@0: extras->domPathPrefix.Assign(extras->jsPathPrefix); michael@0: extras->domPathPrefix.AppendLiteral("/dom/"); michael@0: extras->jsPathPrefix.AppendLiteral("/js-"); michael@0: needZone = false; michael@0: } else { michael@0: extras->jsPathPrefix.AssignLiteral("explicit/js-non-window/zones/"); michael@0: extras->domPathPrefix.AssignLiteral("explicit/dom/unknown-window-global?!/"); michael@0: } michael@0: } else { michael@0: extras->jsPathPrefix.AssignLiteral("explicit/js-non-window/zones/"); michael@0: extras->domPathPrefix.AssignLiteral("explicit/dom/non-window-global?!/"); michael@0: } michael@0: } else { michael@0: extras->jsPathPrefix.AssignLiteral("explicit/js-non-window/zones/"); michael@0: extras->domPathPrefix.AssignLiteral("explicit/dom/no-global?!/"); michael@0: } michael@0: michael@0: if (needZone) michael@0: extras->jsPathPrefix += nsPrintfCString("zone(0x%p)/", (void *)js::GetCompartmentZone(c)); michael@0: michael@0: extras->jsPathPrefix += NS_LITERAL_CSTRING("compartment(") + cName + NS_LITERAL_CSTRING(")/"); michael@0: michael@0: // extras->jsPathPrefix is used for almost all the compartment-specific michael@0: // reports. At this point it has the form michael@0: // "compartment()/". michael@0: // michael@0: // extras->domPathPrefix is used for DOM orphan nodes, which are michael@0: // counted by the JS reporter but reported as part of the DOM michael@0: // measurements. At this point it has the form "/dom/" if michael@0: // this compartment belongs to an nsGlobalWindow, and michael@0: // "explicit/dom/?!/" otherwise (in which case it shouldn't michael@0: // be used, because non-nsGlobalWindow compartments shouldn't have michael@0: // orphan DOM nodes). michael@0: michael@0: MOZ_ASSERT(StartsWithExplicit(extras->jsPathPrefix)); michael@0: MOZ_ASSERT(StartsWithExplicit(extras->domPathPrefix)); michael@0: michael@0: cstats->extra = extras; michael@0: } michael@0: }; michael@0: michael@0: nsresult michael@0: JSReporter::CollectReports(WindowPaths *windowPaths, michael@0: WindowPaths *topWindowPaths, michael@0: nsIMemoryReporterCallback *cb, michael@0: nsISupports *closure) michael@0: { michael@0: XPCJSRuntime *xpcrt = nsXPConnect::GetRuntimeInstance(); michael@0: michael@0: // In the first step we get all the stats and stash them in a local michael@0: // data structure. In the second step we pass all the stashed stats to michael@0: // the callback. Separating these steps is important because the michael@0: // callback may be a JS function, and executing JS while getting these michael@0: // stats seems like a bad idea. michael@0: michael@0: nsCOMPtr addonManager; michael@0: if (XRE_GetProcessType() == GeckoProcessType_Default) { michael@0: // Only try to access the service from the main process. michael@0: addonManager = do_GetService("@mozilla.org/addons/integration;1"); michael@0: } michael@0: bool getLocations = !!addonManager; michael@0: XPCJSRuntimeStats rtStats(windowPaths, topWindowPaths, getLocations); michael@0: OrphanReporter orphanReporter(XPCConvert::GetISupportsFromJSObject); michael@0: if (!JS::CollectRuntimeStats(xpcrt->Runtime(), &rtStats, &orphanReporter)) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: size_t xpconnect = xpcrt->SizeOfIncludingThis(JSMallocSizeOf); michael@0: michael@0: XPCWrappedNativeScope::ScopeSizeInfo sizeInfo(JSMallocSizeOf); michael@0: XPCWrappedNativeScope::AddSizeOfAllScopesIncludingThis(&sizeInfo); michael@0: michael@0: mozJSComponentLoader* loader = mozJSComponentLoader::Get(); michael@0: size_t jsComponentLoaderSize = loader ? loader->SizeOfIncludingThis(JSMallocSizeOf) : 0; michael@0: michael@0: // This is the second step (see above). First we report stuff in the michael@0: // "explicit" tree, then we report other stuff. michael@0: michael@0: nsresult rv; michael@0: size_t rtTotal = 0; michael@0: rv = xpc::ReportJSRuntimeExplicitTreeStats(rtStats, michael@0: NS_LITERAL_CSTRING("explicit/js-non-window/"), michael@0: addonManager, cb, closure, michael@0: &rtTotal); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Report the sums of the compartment numbers. michael@0: xpc::CompartmentStatsExtras cExtrasTotal; michael@0: cExtrasTotal.jsPathPrefix.AssignLiteral("js-main-runtime/compartments/"); michael@0: cExtrasTotal.domPathPrefix.AssignLiteral("window-objects/dom/"); michael@0: rv = ReportCompartmentStats(rtStats.cTotals, cExtrasTotal, addonManager, michael@0: cb, closure); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: xpc::ZoneStatsExtras zExtrasTotal; michael@0: zExtrasTotal.pathPrefix.AssignLiteral("js-main-runtime/zones/"); michael@0: rv = ReportZoneStats(rtStats.zTotals, zExtrasTotal, cb, closure); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Report the sum of the runtime/ numbers. michael@0: REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime/runtime"), michael@0: KIND_OTHER, rtTotal, michael@0: "The sum of all measurements under 'explicit/js-non-window/runtime/'."); michael@0: michael@0: // Report the numbers for memory outside of compartments. michael@0: michael@0: REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime/gc-heap/unused-chunks"), michael@0: KIND_OTHER, rtStats.gcHeapUnusedChunks, michael@0: "The same as 'explicit/js-non-window/gc-heap/unused-chunks'."); michael@0: michael@0: REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime/gc-heap/unused-arenas"), michael@0: KIND_OTHER, rtStats.gcHeapUnusedArenas, michael@0: "The same as 'explicit/js-non-window/gc-heap/unused-arenas'."); michael@0: michael@0: REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime/gc-heap/chunk-admin"), michael@0: KIND_OTHER, rtStats.gcHeapChunkAdmin, michael@0: "The same as 'explicit/js-non-window/gc-heap/chunk-admin'."); michael@0: michael@0: // Report a breakdown of the committed GC space. michael@0: michael@0: REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/unused/chunks"), michael@0: KIND_OTHER, rtStats.gcHeapUnusedChunks, michael@0: "The same as 'explicit/js-non-window/gc-heap/unused-chunks'."); michael@0: michael@0: REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/unused/arenas"), michael@0: KIND_OTHER, rtStats.gcHeapUnusedArenas, michael@0: "The same as 'explicit/js-non-window/gc-heap/unused-arenas'."); michael@0: michael@0: REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/unused/gc-things"), michael@0: KIND_OTHER, rtStats.zTotals.unusedGCThings, michael@0: "The same as 'js-main-runtime/zones/unused-gc-things'."); michael@0: michael@0: REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/used/chunk-admin"), michael@0: KIND_OTHER, rtStats.gcHeapChunkAdmin, michael@0: "The same as 'explicit/js-non-window/gc-heap/chunk-admin'."); michael@0: michael@0: REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/used/arena-admin"), michael@0: KIND_OTHER, rtStats.zTotals.gcHeapArenaAdmin, michael@0: "The same as 'js-main-runtime/zones/gc-heap-arena-admin'."); michael@0: michael@0: REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/used/gc-things"), michael@0: KIND_OTHER, rtStats.gcHeapGCThings, michael@0: "GC things: objects, strings, scripts, etc."); michael@0: michael@0: // Report xpconnect. michael@0: michael@0: REPORT_BYTES(NS_LITERAL_CSTRING("explicit/xpconnect/runtime"), michael@0: KIND_HEAP, xpconnect, michael@0: "The XPConnect runtime."); michael@0: michael@0: REPORT_BYTES(NS_LITERAL_CSTRING("explicit/xpconnect/scopes"), michael@0: KIND_HEAP, sizeInfo.mScopeAndMapSize, michael@0: "XPConnect scopes."); michael@0: michael@0: REPORT_BYTES(NS_LITERAL_CSTRING("explicit/xpconnect/proto-iface-cache"), michael@0: KIND_HEAP, sizeInfo.mProtoAndIfaceCacheSize, michael@0: "Prototype and interface binding caches."); michael@0: michael@0: REPORT_BYTES(NS_LITERAL_CSTRING("explicit/xpconnect/js-component-loader"), michael@0: KIND_HEAP, jsComponentLoaderSize, michael@0: "XPConnect's JS component loader."); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: static nsresult michael@0: JSSizeOfTab(JSObject *objArg, size_t *jsObjectsSize, size_t *jsStringsSize, michael@0: size_t *jsPrivateSize, size_t *jsOtherSize) michael@0: { michael@0: JSRuntime *rt = nsXPConnect::GetRuntimeInstance()->Runtime(); michael@0: JS::RootedObject obj(rt, objArg); michael@0: michael@0: TabSizes sizes; michael@0: OrphanReporter orphanReporter(XPCConvert::GetISupportsFromJSObject); michael@0: NS_ENSURE_TRUE(JS::AddSizeOfTab(rt, obj, moz_malloc_size_of, michael@0: &orphanReporter, &sizes), michael@0: NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: *jsObjectsSize = sizes.objects; michael@0: *jsStringsSize = sizes.strings; michael@0: *jsPrivateSize = sizes.private_; michael@0: *jsOtherSize = sizes.other; michael@0: return NS_OK; michael@0: } michael@0: michael@0: } // namespace xpc michael@0: michael@0: #ifdef MOZ_CRASHREPORTER michael@0: static bool michael@0: DiagnosticMemoryCallback(void *ptr, size_t size) michael@0: { michael@0: return CrashReporter::RegisterAppMemory(ptr, size) == NS_OK; michael@0: } michael@0: #endif michael@0: michael@0: static void michael@0: AccumulateTelemetryCallback(int id, uint32_t sample) michael@0: { michael@0: switch (id) { michael@0: case JS_TELEMETRY_GC_REASON: michael@0: Telemetry::Accumulate(Telemetry::GC_REASON_2, sample); michael@0: break; michael@0: case JS_TELEMETRY_GC_IS_COMPARTMENTAL: michael@0: Telemetry::Accumulate(Telemetry::GC_IS_COMPARTMENTAL, sample); michael@0: break; michael@0: case JS_TELEMETRY_GC_MS: michael@0: Telemetry::Accumulate(Telemetry::GC_MS, sample); michael@0: break; michael@0: case JS_TELEMETRY_GC_MAX_PAUSE_MS: michael@0: Telemetry::Accumulate(Telemetry::GC_MAX_PAUSE_MS, sample); michael@0: break; michael@0: case JS_TELEMETRY_GC_MARK_MS: michael@0: Telemetry::Accumulate(Telemetry::GC_MARK_MS, sample); michael@0: break; michael@0: case JS_TELEMETRY_GC_SWEEP_MS: michael@0: Telemetry::Accumulate(Telemetry::GC_SWEEP_MS, sample); michael@0: break; michael@0: case JS_TELEMETRY_GC_MARK_ROOTS_MS: michael@0: Telemetry::Accumulate(Telemetry::GC_MARK_ROOTS_MS, sample); michael@0: break; michael@0: case JS_TELEMETRY_GC_MARK_GRAY_MS: michael@0: Telemetry::Accumulate(Telemetry::GC_MARK_GRAY_MS, sample); michael@0: break; michael@0: case JS_TELEMETRY_GC_SLICE_MS: michael@0: Telemetry::Accumulate(Telemetry::GC_SLICE_MS, sample); michael@0: break; michael@0: case JS_TELEMETRY_GC_MMU_50: michael@0: Telemetry::Accumulate(Telemetry::GC_MMU_50, sample); michael@0: break; michael@0: case JS_TELEMETRY_GC_RESET: michael@0: Telemetry::Accumulate(Telemetry::GC_RESET, sample); michael@0: break; michael@0: case JS_TELEMETRY_GC_INCREMENTAL_DISABLED: michael@0: Telemetry::Accumulate(Telemetry::GC_INCREMENTAL_DISABLED, sample); michael@0: break; michael@0: case JS_TELEMETRY_GC_NON_INCREMENTAL: michael@0: Telemetry::Accumulate(Telemetry::GC_NON_INCREMENTAL, sample); michael@0: break; michael@0: case JS_TELEMETRY_GC_SCC_SWEEP_TOTAL_MS: michael@0: Telemetry::Accumulate(Telemetry::GC_SCC_SWEEP_TOTAL_MS, sample); michael@0: break; michael@0: case JS_TELEMETRY_GC_SCC_SWEEP_MAX_PAUSE_MS: michael@0: Telemetry::Accumulate(Telemetry::GC_SCC_SWEEP_MAX_PAUSE_MS, sample); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: static void michael@0: CompartmentNameCallback(JSRuntime *rt, JSCompartment *comp, michael@0: char *buf, size_t bufsize) michael@0: { michael@0: nsCString name; michael@0: GetCompartmentName(comp, name, false); michael@0: if (name.Length() >= bufsize) michael@0: name.Truncate(bufsize - 1); michael@0: memcpy(buf, name.get(), name.Length() + 1); michael@0: } michael@0: michael@0: static bool michael@0: PreserveWrapper(JSContext *cx, JSObject *obj) michael@0: { michael@0: MOZ_ASSERT(cx); michael@0: MOZ_ASSERT(obj); michael@0: MOZ_ASSERT(IS_WN_REFLECTOR(obj) || mozilla::dom::IsDOMObject(obj)); michael@0: michael@0: return mozilla::dom::IsDOMObject(obj) && mozilla::dom::TryPreserveWrapper(obj); michael@0: } michael@0: michael@0: static nsresult michael@0: ReadSourceFromFilename(JSContext *cx, const char *filename, jschar **src, size_t *len) michael@0: { michael@0: nsresult rv; michael@0: michael@0: // mozJSSubScriptLoader prefixes the filenames of the scripts it loads with michael@0: // the filename of its caller. Axe that if present. michael@0: const char *arrow; michael@0: while ((arrow = strstr(filename, " -> "))) michael@0: filename = arrow + strlen(" -> "); michael@0: michael@0: // Get the URI. michael@0: nsCOMPtr uri; michael@0: rv = NS_NewURI(getter_AddRefs(uri), filename); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr scriptChannel; michael@0: rv = NS_NewChannel(getter_AddRefs(scriptChannel), uri); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Only allow local reading. michael@0: nsCOMPtr actualUri; michael@0: rv = scriptChannel->GetURI(getter_AddRefs(actualUri)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: nsCString scheme; michael@0: rv = actualUri->GetScheme(scheme); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (!scheme.EqualsLiteral("file") && !scheme.EqualsLiteral("jar")) michael@0: return NS_OK; michael@0: michael@0: nsCOMPtr scriptStream; michael@0: rv = scriptChannel->Open(getter_AddRefs(scriptStream)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: uint64_t rawLen; michael@0: rv = scriptStream->Available(&rawLen); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (!rawLen) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // Technically, this should be SIZE_MAX, but we don't run on machines michael@0: // where that would be less than UINT32_MAX, and the latter is already michael@0: // well beyond a reasonable limit. michael@0: if (rawLen > UINT32_MAX) michael@0: return NS_ERROR_FILE_TOO_BIG; michael@0: michael@0: // Allocate an internal buf the size of the file. michael@0: nsAutoArrayPtr buf(new unsigned char[rawLen]); michael@0: if (!buf) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: unsigned char *ptr = buf, *end = ptr + rawLen; michael@0: while (ptr < end) { michael@0: uint32_t bytesRead; michael@0: rv = scriptStream->Read(reinterpret_cast(ptr), end - ptr, &bytesRead); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: MOZ_ASSERT(bytesRead > 0, "stream promised more bytes before EOF"); michael@0: ptr += bytesRead; michael@0: } michael@0: michael@0: rv = nsScriptLoader::ConvertToUTF16(scriptChannel, buf, rawLen, EmptyString(), michael@0: nullptr, *src, *len); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!*src) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // Historically this method used JS_malloc() which updates the GC memory michael@0: // accounting. Since ConvertToUTF16() now uses js_malloc() instead we michael@0: // update the accounting manually after the fact. michael@0: JS_updateMallocCounter(cx, *len); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // The JS engine calls this object's 'load' member function when it needs michael@0: // the source for a chrome JS function. See the comment in the XPCJSRuntime michael@0: // constructor. michael@0: class XPCJSSourceHook: public js::SourceHook { michael@0: bool load(JSContext *cx, const char *filename, jschar **src, size_t *length) { michael@0: *src = nullptr; michael@0: *length = 0; michael@0: michael@0: if (!nsContentUtils::IsCallerChrome()) michael@0: return true; michael@0: michael@0: if (!filename) michael@0: return true; michael@0: michael@0: nsresult rv = ReadSourceFromFilename(cx, filename, src, length); michael@0: if (NS_FAILED(rv)) { michael@0: xpc::Throw(cx, rv); michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: }; michael@0: michael@0: static const JSWrapObjectCallbacks WrapObjectCallbacks = { michael@0: xpc::WrapperFactory::Rewrap, michael@0: xpc::WrapperFactory::PrepareForWrapping michael@0: }; michael@0: michael@0: XPCJSRuntime::XPCJSRuntime(nsXPConnect* aXPConnect) michael@0: : CycleCollectedJSRuntime(nullptr, 32L * 1024L * 1024L, JS_USE_HELPER_THREADS), michael@0: mJSContextStack(new XPCJSContextStack(MOZ_THIS_IN_INITIALIZER_LIST())), michael@0: mCallContext(nullptr), michael@0: mAutoRoots(nullptr), michael@0: mResolveName(JSID_VOID), michael@0: mResolvingWrapper(nullptr), michael@0: mWrappedJSMap(JSObject2WrappedJSMap::newMap(XPC_JS_MAP_SIZE)), michael@0: mWrappedJSClassMap(IID2WrappedJSClassMap::newMap(XPC_JS_CLASS_MAP_SIZE)), michael@0: mIID2NativeInterfaceMap(IID2NativeInterfaceMap::newMap(XPC_NATIVE_INTERFACE_MAP_SIZE)), michael@0: mClassInfo2NativeSetMap(ClassInfo2NativeSetMap::newMap(XPC_NATIVE_SET_MAP_SIZE)), michael@0: mNativeSetMap(NativeSetMap::newMap(XPC_NATIVE_SET_MAP_SIZE)), michael@0: mThisTranslatorMap(IID2ThisTranslatorMap::newMap(XPC_THIS_TRANSLATOR_MAP_SIZE)), michael@0: mNativeScriptableSharedMap(XPCNativeScriptableSharedMap::newMap(XPC_NATIVE_JSCLASS_MAP_SIZE)), michael@0: mDyingWrappedNativeProtoMap(XPCWrappedNativeProtoMap::newMap(XPC_DYING_NATIVE_PROTO_MAP_SIZE)), michael@0: mDetachedWrappedNativeProtoMap(XPCWrappedNativeProtoMap::newMap(XPC_DETACHED_NATIVE_PROTO_MAP_SIZE)), michael@0: mGCIsRunning(false), michael@0: mWrappedJSToReleaseArray(), michael@0: mNativesToReleaseArray(), michael@0: mDoingFinalization(false), michael@0: mVariantRoots(nullptr), michael@0: mWrappedJSRoots(nullptr), michael@0: mObjectHolderRoots(nullptr), michael@0: mWatchdogManager(new WatchdogManager(MOZ_THIS_IN_INITIALIZER_LIST())), michael@0: mJunkScope(MOZ_THIS_IN_INITIALIZER_LIST()->Runtime(), nullptr), michael@0: mCompilationScope(MOZ_THIS_IN_INITIALIZER_LIST()->Runtime(), nullptr), michael@0: mAsyncSnowWhiteFreer(new AsyncFreeSnowWhite()) michael@0: { michael@0: DOM_InitInterfaces(); michael@0: michael@0: // these jsids filled in later when we have a JSContext to work with. michael@0: mStrIDs[0] = JSID_VOID; michael@0: michael@0: MOZ_ASSERT(Runtime()); michael@0: JSRuntime* runtime = Runtime(); michael@0: michael@0: auto rtPrivate = new PerThreadAtomCache(); michael@0: memset(rtPrivate, 0, sizeof(PerThreadAtomCache)); michael@0: JS_SetRuntimePrivate(runtime, rtPrivate); michael@0: michael@0: // Unconstrain the runtime's threshold on nominal heap size, to avoid michael@0: // triggering GC too often if operating continuously near an arbitrary michael@0: // finite threshold (0xffffffff is infinity for uint32_t parameters). michael@0: // This leaves the maximum-JS_malloc-bytes threshold still in effect michael@0: // to cause period, and we hope hygienic, last-ditch GCs from within michael@0: // the GC's allocator. michael@0: JS_SetGCParameter(runtime, JSGC_MAX_BYTES, 0xffffffff); michael@0: michael@0: // The JS engine permits us to set different stack limits for system code, michael@0: // trusted script, and untrusted script. We have tests that ensure that michael@0: // we can always execute 10 "heavy" (eval+with) stack frames deeper in michael@0: // privileged code. Our stack sizes vary greatly in different configurations, michael@0: // so satisfying those tests requires some care. Manual measurements of the michael@0: // number of heavy stack frames achievable gives us the following rough data, michael@0: // ordered by the effective categories in which they are grouped in the michael@0: // JS_SetNativeStackQuota call (which predates this analysis). michael@0: // michael@0: // (NB: These numbers may have drifted recently - see bug 938429) michael@0: // OSX 64-bit Debug: 7MB stack, 636 stack frames => ~11.3k per stack frame michael@0: // OSX64 Opt: 7MB stack, 2440 stack frames => ~3k per stack frame michael@0: // michael@0: // Linux 32-bit Debug: 2MB stack, 447 stack frames => ~4.6k per stack frame michael@0: // Linux 64-bit Debug: 4MB stack, 501 stack frames => ~8.2k per stack frame michael@0: // michael@0: // Windows (Opt+Debug): 900K stack, 235 stack frames => ~3.4k per stack frame michael@0: // michael@0: // Linux 32-bit Opt: 1MB stack, 272 stack frames => ~3.8k per stack frame michael@0: // Linux 64-bit Opt: 2MB stack, 316 stack frames => ~6.5k per stack frame michael@0: // michael@0: // We tune the trusted/untrusted quotas for each configuration to achieve our michael@0: // invariants while attempting to minimize overhead. In contrast, our buffer michael@0: // between system code and trusted script is a very unscientific 10k. michael@0: const size_t kSystemCodeBuffer = 10 * 1024; michael@0: michael@0: // Our "default" stack is what we use in configurations where we don't have michael@0: // a compelling reason to do things differently. This is effectively 1MB on michael@0: // 32-bit platforms and 2MB on 64-bit platforms. michael@0: const size_t kDefaultStackQuota = 128 * sizeof(size_t) * 1024; michael@0: michael@0: // Set stack sizes for different configurations. It's probably not great for michael@0: // the web to base this decision primarily on the default stack size that the michael@0: // underlying platform makes available, but that seems to be what we do. :-( michael@0: michael@0: #if defined(XP_MACOSX) || defined(DARWIN) michael@0: // MacOS has a gargantuan default stack size of 8MB. Go wild with 7MB, michael@0: // and give trusted script 140k extra. The stack is huge on mac anyway. michael@0: const size_t kStackQuota = 7 * 1024 * 1024; michael@0: const size_t kTrustedScriptBuffer = 140 * 1024; michael@0: #elif defined(MOZ_ASAN) michael@0: // ASan requires more stack space due to red-zones, so give it double the michael@0: // default (2MB on 32-bit, 4MB on 64-bit). ASAN stack frame measurements michael@0: // were not taken at the time of this writing, so we hazard a guess that michael@0: // ASAN builds have roughly thrice the stack overhead as normal builds. michael@0: // On normal builds, the largest stack frame size we might encounter is michael@0: // 8.2k, so let's use a buffer of 8.2 * 3 * 10 = 246k. michael@0: const size_t kStackQuota = 2 * kDefaultStackQuota; michael@0: const size_t kTrustedScriptBuffer = 246 * 1024; michael@0: #elif defined(XP_WIN) michael@0: // 1MB is the default stack size on Windows, so the default 1MB stack quota michael@0: // we'd get on win32 is slightly too large. Use 900k instead. And since michael@0: // windows stack frames are 3.4k each, let's use a buffer of 50k. michael@0: const size_t kStackQuota = 900 * 1024; michael@0: const size_t kTrustedScriptBuffer = 50 * 1024; michael@0: // The following two configurations are linux-only. Given the numbers above, michael@0: // we use 50k and 100k trusted buffers on 32-bit and 64-bit respectively. michael@0: #elif defined(DEBUG) michael@0: // Bug 803182: account for the 4x difference in the size of js::Interpret michael@0: // between optimized and debug builds. michael@0: // XXXbholley - Then why do we only account for 2x of difference? michael@0: const size_t kStackQuota = 2 * kDefaultStackQuota; michael@0: const size_t kTrustedScriptBuffer = sizeof(size_t) * 12800; michael@0: #else michael@0: const size_t kStackQuota = kDefaultStackQuota; michael@0: const size_t kTrustedScriptBuffer = sizeof(size_t) * 12800; michael@0: #endif michael@0: michael@0: // Avoid an unused variable warning on platforms where we don't use the michael@0: // default. michael@0: (void) kDefaultStackQuota; michael@0: michael@0: JS_SetNativeStackQuota(runtime, michael@0: kStackQuota, michael@0: kStackQuota - kSystemCodeBuffer, michael@0: kStackQuota - kSystemCodeBuffer - kTrustedScriptBuffer); michael@0: michael@0: JS_SetDestroyCompartmentCallback(runtime, CompartmentDestroyedCallback); michael@0: JS_SetCompartmentNameCallback(runtime, CompartmentNameCallback); michael@0: mPrevGCSliceCallback = JS::SetGCSliceCallback(runtime, GCSliceCallback); michael@0: JS_SetFinalizeCallback(runtime, FinalizeCallback); michael@0: JS_SetWrapObjectCallbacks(runtime, &WrapObjectCallbacks); michael@0: js::SetPreserveWrapperCallback(runtime, PreserveWrapper); michael@0: #ifdef MOZ_CRASHREPORTER michael@0: JS_EnumerateDiagnosticMemoryRegions(DiagnosticMemoryCallback); michael@0: #endif michael@0: #ifdef MOZ_ENABLE_PROFILER_SPS michael@0: if (PseudoStack *stack = mozilla_get_pseudo_stack()) michael@0: stack->sampleRuntime(runtime); michael@0: #endif michael@0: JS_SetAccumulateTelemetryCallback(runtime, AccumulateTelemetryCallback); michael@0: js::SetDefaultJSContextCallback(runtime, DefaultJSContextCallback); michael@0: js::SetActivityCallback(runtime, ActivityCallback, this); michael@0: js::SetCTypesActivityCallback(runtime, CTypesActivityCallback); michael@0: JS_SetInterruptCallback(runtime, InterruptCallback); michael@0: JS::SetOutOfMemoryCallback(runtime, OutOfMemoryCallback); michael@0: michael@0: // The JS engine needs to keep the source code around in order to implement michael@0: // Function.prototype.toSource(). It'd be nice to not have to do this for michael@0: // chrome code and simply stub out requests for source on it. Life is not so michael@0: // easy, unfortunately. Nobody relies on chrome toSource() working in core michael@0: // browser code, but chrome tests use it. The worst offenders are addons, michael@0: // which like to monkeypatch chrome functions by calling toSource() on them michael@0: // and using regular expressions to modify them. We avoid keeping most browser michael@0: // JS source code in memory by setting LAZY_SOURCE on JS::CompileOptions when michael@0: // compiling some chrome code. This causes the JS engine not save the source michael@0: // code in memory. When the JS engine is asked to provide the source for a michael@0: // function compiled with LAZY_SOURCE, it calls SourceHook to load it. michael@0: /// michael@0: // Note we do have to retain the source code in memory for scripts compiled in michael@0: // compileAndGo mode and compiled function bodies (from michael@0: // JS_CompileFunction*). In practice, this means content scripts and event michael@0: // handlers. michael@0: js::SetSourceHook(runtime, new XPCJSSourceHook); michael@0: michael@0: // Set up locale information and callbacks for the newly-created runtime so michael@0: // that the various toLocaleString() methods, localeCompare(), and other michael@0: // internationalization APIs work as desired. michael@0: if (!xpc_LocalizeRuntime(runtime)) michael@0: NS_RUNTIMEABORT("xpc_LocalizeRuntime failed."); michael@0: michael@0: // Register memory reporters and distinguished amount functions. michael@0: RegisterStrongMemoryReporter(new JSMainRuntimeCompartmentsReporter()); michael@0: RegisterStrongMemoryReporter(new JSMainRuntimeTemporaryPeakReporter()); michael@0: RegisterJSMainRuntimeGCHeapDistinguishedAmount(JSMainRuntimeGCHeapDistinguishedAmount); michael@0: RegisterJSMainRuntimeTemporaryPeakDistinguishedAmount(JSMainRuntimeTemporaryPeakDistinguishedAmount); michael@0: RegisterJSMainRuntimeCompartmentsSystemDistinguishedAmount(JSMainRuntimeCompartmentsSystemDistinguishedAmount); michael@0: RegisterJSMainRuntimeCompartmentsUserDistinguishedAmount(JSMainRuntimeCompartmentsUserDistinguishedAmount); michael@0: mozilla::RegisterJSSizeOfTab(JSSizeOfTab); michael@0: michael@0: // Install a JavaScript 'debugger' keyword handler in debug builds only michael@0: #ifdef DEBUG michael@0: if (!JS_GetGlobalDebugHooks(runtime)->debuggerHandler) michael@0: xpc_InstallJSDebuggerKeywordHandler(runtime); michael@0: #endif michael@0: michael@0: // Watch for the JS boolean options. michael@0: ReloadPrefsCallback(nullptr, this); michael@0: Preferences::RegisterCallback(ReloadPrefsCallback, JS_OPTIONS_DOT_STR, this); michael@0: } michael@0: michael@0: // static michael@0: XPCJSRuntime* michael@0: XPCJSRuntime::newXPCJSRuntime(nsXPConnect* aXPConnect) michael@0: { michael@0: NS_PRECONDITION(aXPConnect,"bad param"); michael@0: michael@0: XPCJSRuntime* self = new XPCJSRuntime(aXPConnect); michael@0: michael@0: if (self && michael@0: self->Runtime() && michael@0: self->GetWrappedJSMap() && michael@0: self->GetWrappedJSClassMap() && michael@0: self->GetIID2NativeInterfaceMap() && michael@0: self->GetClassInfo2NativeSetMap() && michael@0: self->GetNativeSetMap() && michael@0: self->GetThisTranslatorMap() && michael@0: self->GetNativeScriptableSharedMap() && michael@0: self->GetDyingWrappedNativeProtoMap() && michael@0: self->mWatchdogManager) { michael@0: return self; michael@0: } michael@0: michael@0: NS_RUNTIMEABORT("new XPCJSRuntime failed to initialize."); michael@0: michael@0: delete self; michael@0: return nullptr; michael@0: } michael@0: michael@0: bool michael@0: XPCJSRuntime::OnJSContextNew(JSContext *cx) michael@0: { michael@0: // If we were the first cx ever created (like the SafeJSContext), the caller michael@0: // would have had no way to enter a request. Enter one now before doing the michael@0: // rest of the cx setup. michael@0: JSAutoRequest ar(cx); michael@0: michael@0: // if it is our first context then we need to generate our string ids michael@0: if (JSID_IS_VOID(mStrIDs[0])) { michael@0: RootedString str(cx); michael@0: for (unsigned i = 0; i < IDX_TOTAL_COUNT; i++) { michael@0: str = JS_InternString(cx, mStrings[i]); michael@0: if (!str) { michael@0: mStrIDs[0] = JSID_VOID; michael@0: return false; michael@0: } michael@0: mStrIDs[i] = INTERNED_STRING_TO_JSID(cx, str); michael@0: mStrJSVals[i] = STRING_TO_JSVAL(str); michael@0: } michael@0: michael@0: if (!mozilla::dom::DefineStaticJSVals(cx)) { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: XPCContext* xpc = new XPCContext(this, cx); michael@0: if (!xpc) michael@0: return false; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: XPCJSRuntime::DescribeCustomObjects(JSObject* obj, const js::Class* clasp, michael@0: char (&name)[72]) const michael@0: { michael@0: XPCNativeScriptableInfo *si = nullptr; michael@0: michael@0: if (!IS_PROTO_CLASS(clasp)) { michael@0: return false; michael@0: } michael@0: michael@0: XPCWrappedNativeProto *p = michael@0: static_cast(xpc_GetJSPrivate(obj)); michael@0: si = p->GetScriptableInfo(); michael@0: michael@0: if (!si) { michael@0: return false; michael@0: } michael@0: michael@0: JS_snprintf(name, sizeof(name), "JS Object (%s - %s)", michael@0: clasp->name, si->GetJSClass()->name); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: XPCJSRuntime::NoteCustomGCThingXPCOMChildren(const js::Class* clasp, JSObject* obj, michael@0: nsCycleCollectionTraversalCallback& cb) const michael@0: { michael@0: if (clasp != &XPC_WN_Tearoff_JSClass) { michael@0: return false; michael@0: } michael@0: michael@0: // A tearoff holds a strong reference to its native object michael@0: // (see XPCWrappedNative::FlatJSObjectFinalized). Its XPCWrappedNative michael@0: // will be held alive through the parent of the JSObject of the tearoff. michael@0: XPCWrappedNativeTearOff *to = michael@0: static_cast(xpc_GetJSPrivate(obj)); michael@0: NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "xpc_GetJSPrivate(obj)->mNative"); michael@0: cb.NoteXPCOMChild(to->GetNative()); michael@0: return true; michael@0: } michael@0: michael@0: /***************************************************************************/ michael@0: michael@0: #ifdef DEBUG michael@0: static PLDHashOperator michael@0: WrappedJSClassMapDumpEnumerator(PLDHashTable *table, PLDHashEntryHdr *hdr, michael@0: uint32_t number, void *arg) michael@0: { michael@0: ((IID2WrappedJSClassMap::Entry*)hdr)->value->DebugDump(*(int16_t*)arg); michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: static PLDHashOperator michael@0: NativeSetDumpEnumerator(PLDHashTable *table, PLDHashEntryHdr *hdr, michael@0: uint32_t number, void *arg) michael@0: { michael@0: ((NativeSetMap::Entry*)hdr)->key_value->DebugDump(*(int16_t*)arg); michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: #endif michael@0: michael@0: void michael@0: XPCJSRuntime::DebugDump(int16_t depth) michael@0: { michael@0: #ifdef DEBUG michael@0: depth--; michael@0: XPC_LOG_ALWAYS(("XPCJSRuntime @ %x", this)); michael@0: XPC_LOG_INDENT(); michael@0: XPC_LOG_ALWAYS(("mJSRuntime @ %x", Runtime())); michael@0: michael@0: XPC_LOG_ALWAYS(("mWrappedJSToReleaseArray @ %x with %d wrappers(s)", \ michael@0: &mWrappedJSToReleaseArray, michael@0: mWrappedJSToReleaseArray.Length())); michael@0: michael@0: int cxCount = 0; michael@0: JSContext* iter = nullptr; michael@0: while (JS_ContextIterator(Runtime(), &iter)) michael@0: ++cxCount; michael@0: XPC_LOG_ALWAYS(("%d JS context(s)", cxCount)); michael@0: michael@0: iter = nullptr; michael@0: while (JS_ContextIterator(Runtime(), &iter)) { michael@0: XPCContext *xpc = XPCContext::GetXPCContext(iter); michael@0: XPC_LOG_INDENT(); michael@0: xpc->DebugDump(depth); michael@0: XPC_LOG_OUTDENT(); michael@0: } michael@0: michael@0: XPC_LOG_ALWAYS(("mWrappedJSClassMap @ %x with %d wrapperclasses(s)", \ michael@0: mWrappedJSClassMap, mWrappedJSClassMap ? \ michael@0: mWrappedJSClassMap->Count() : 0)); michael@0: // iterate wrappersclasses... michael@0: if (depth && mWrappedJSClassMap && mWrappedJSClassMap->Count()) { michael@0: XPC_LOG_INDENT(); michael@0: mWrappedJSClassMap->Enumerate(WrappedJSClassMapDumpEnumerator, &depth); michael@0: XPC_LOG_OUTDENT(); michael@0: } michael@0: XPC_LOG_ALWAYS(("mWrappedJSMap @ %x with %d wrappers(s)", \ michael@0: mWrappedJSMap, mWrappedJSMap ? \ michael@0: mWrappedJSMap->Count() : 0)); michael@0: // iterate wrappers... michael@0: if (depth && mWrappedJSMap && mWrappedJSMap->Count()) { michael@0: XPC_LOG_INDENT(); michael@0: mWrappedJSMap->Dump(depth); michael@0: XPC_LOG_OUTDENT(); michael@0: } michael@0: michael@0: XPC_LOG_ALWAYS(("mIID2NativeInterfaceMap @ %x with %d interface(s)", \ michael@0: mIID2NativeInterfaceMap, mIID2NativeInterfaceMap ? \ michael@0: mIID2NativeInterfaceMap->Count() : 0)); michael@0: michael@0: XPC_LOG_ALWAYS(("mClassInfo2NativeSetMap @ %x with %d sets(s)", \ michael@0: mClassInfo2NativeSetMap, mClassInfo2NativeSetMap ? \ michael@0: mClassInfo2NativeSetMap->Count() : 0)); michael@0: michael@0: XPC_LOG_ALWAYS(("mThisTranslatorMap @ %x with %d translator(s)", \ michael@0: mThisTranslatorMap, mThisTranslatorMap ? \ michael@0: mThisTranslatorMap->Count() : 0)); michael@0: michael@0: XPC_LOG_ALWAYS(("mNativeSetMap @ %x with %d sets(s)", \ michael@0: mNativeSetMap, mNativeSetMap ? \ michael@0: mNativeSetMap->Count() : 0)); michael@0: michael@0: // iterate sets... michael@0: if (depth && mNativeSetMap && mNativeSetMap->Count()) { michael@0: XPC_LOG_INDENT(); michael@0: mNativeSetMap->Enumerate(NativeSetDumpEnumerator, &depth); michael@0: XPC_LOG_OUTDENT(); michael@0: } michael@0: michael@0: XPC_LOG_OUTDENT(); michael@0: #endif michael@0: } michael@0: michael@0: /***************************************************************************/ michael@0: michael@0: void michael@0: XPCRootSetElem::AddToRootSet(XPCRootSetElem **listHead) michael@0: { michael@0: MOZ_ASSERT(!mSelfp, "Must be not linked"); michael@0: michael@0: mSelfp = listHead; michael@0: mNext = *listHead; michael@0: if (mNext) { michael@0: MOZ_ASSERT(mNext->mSelfp == listHead, "Must be list start"); michael@0: mNext->mSelfp = &mNext; michael@0: } michael@0: *listHead = this; michael@0: } michael@0: michael@0: void michael@0: XPCRootSetElem::RemoveFromRootSet() michael@0: { michael@0: nsXPConnect *xpc = nsXPConnect::XPConnect(); michael@0: JS::PokeGC(xpc->GetRuntime()->Runtime()); michael@0: michael@0: MOZ_ASSERT(mSelfp, "Must be linked"); michael@0: michael@0: MOZ_ASSERT(*mSelfp == this, "Link invariant"); michael@0: *mSelfp = mNext; michael@0: if (mNext) michael@0: mNext->mSelfp = mSelfp; michael@0: #ifdef DEBUG michael@0: mSelfp = nullptr; michael@0: mNext = nullptr; michael@0: #endif michael@0: } michael@0: michael@0: void michael@0: XPCJSRuntime::AddGCCallback(xpcGCCallback cb) michael@0: { michael@0: MOZ_ASSERT(cb, "null callback"); michael@0: extraGCCallbacks.AppendElement(cb); michael@0: } michael@0: michael@0: void michael@0: XPCJSRuntime::RemoveGCCallback(xpcGCCallback cb) michael@0: { michael@0: MOZ_ASSERT(cb, "null callback"); michael@0: bool found = extraGCCallbacks.RemoveElement(cb); michael@0: if (!found) { michael@0: NS_ERROR("Removing a callback which was never added."); michael@0: } michael@0: } michael@0: michael@0: void michael@0: XPCJSRuntime::AddContextCallback(xpcContextCallback cb) michael@0: { michael@0: MOZ_ASSERT(cb, "null callback"); michael@0: extraContextCallbacks.AppendElement(cb); michael@0: } michael@0: michael@0: void michael@0: XPCJSRuntime::RemoveContextCallback(xpcContextCallback cb) michael@0: { michael@0: MOZ_ASSERT(cb, "null callback"); michael@0: bool found = extraContextCallbacks.RemoveElement(cb); michael@0: if (!found) { michael@0: NS_ERROR("Removing a callback which was never added."); michael@0: } michael@0: } michael@0: michael@0: JSObject * michael@0: XPCJSRuntime::GetJunkScope() michael@0: { michael@0: if (!mJunkScope) { michael@0: AutoSafeJSContext cx; michael@0: SandboxOptions options; michael@0: options.sandboxName.AssignLiteral("XPConnect Junk Compartment"); michael@0: RootedValue v(cx); michael@0: nsresult rv = CreateSandboxObject(cx, &v, nsContentUtils::GetSystemPrincipal(), options); michael@0: NS_ENSURE_SUCCESS(rv, nullptr); michael@0: michael@0: mJunkScope = js::UncheckedUnwrap(&v.toObject()); michael@0: } michael@0: return mJunkScope; michael@0: } michael@0: michael@0: JSObject * michael@0: XPCJSRuntime::GetCompilationScope() michael@0: { michael@0: if (!mCompilationScope) { michael@0: AutoSafeJSContext cx; michael@0: SandboxOptions options; michael@0: options.sandboxName.AssignLiteral("XPConnect Compilation Compartment"); michael@0: options.invisibleToDebugger = true; michael@0: options.discardSource = ShouldDiscardSystemSource(); michael@0: RootedValue v(cx); michael@0: nsresult rv = CreateSandboxObject(cx, &v, /* principal = */ nullptr, options); michael@0: NS_ENSURE_SUCCESS(rv, nullptr); michael@0: michael@0: mCompilationScope = js::UncheckedUnwrap(&v.toObject()); michael@0: } michael@0: return mCompilationScope; michael@0: } michael@0: michael@0: michael@0: void michael@0: XPCJSRuntime::DeleteSingletonScopes() michael@0: { michael@0: mJunkScope = nullptr; michael@0: mCompilationScope = nullptr; michael@0: }