diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /xpcom/base | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'xpcom/base')
117 files changed, 27150 insertions, 0 deletions
diff --git a/xpcom/base/AvailableMemoryTracker.cpp b/xpcom/base/AvailableMemoryTracker.cpp new file mode 100644 index 000000000..6272d89cf --- /dev/null +++ b/xpcom/base/AvailableMemoryTracker.cpp @@ -0,0 +1,447 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/AvailableMemoryTracker.h" + +#if defined(XP_WIN) +#include "prinrval.h" +#include "prenv.h" +#include "nsIMemoryReporter.h" +#include "nsMemoryPressure.h" +#endif + +#include "nsIObserver.h" +#include "nsIObserverService.h" +#include "nsIRunnable.h" +#include "nsISupports.h" +#include "nsThreadUtils.h" + +#include "mozilla/Preferences.h" +#include "mozilla/Services.h" + +#if defined(XP_WIN) +# include "nsWindowsDllInterceptor.h" +# include <windows.h> +#endif + +#if defined(MOZ_MEMORY) +# include "mozmemory.h" +#endif // MOZ_MEMORY + +using namespace mozilla; + +namespace { + +#if defined(_M_IX86) && defined(XP_WIN) + + +uint32_t sLowVirtualMemoryThreshold = 0; +uint32_t sLowCommitSpaceThreshold = 0; +uint32_t sLowPhysicalMemoryThreshold = 0; +uint32_t sLowMemoryNotificationIntervalMS = 0; + +Atomic<uint32_t> sNumLowVirtualMemEvents; +Atomic<uint32_t> sNumLowCommitSpaceEvents; +Atomic<uint32_t> sNumLowPhysicalMemEvents; + +WindowsDllInterceptor sKernel32Intercept; +WindowsDllInterceptor sGdi32Intercept; + +// Has Init() been called? +bool sInitialized = false; + +// Has Activate() been called? The hooks don't do anything until this happens. +bool sHooksActive = false; + +// Alas, we'd like to use mozilla::TimeStamp, but we can't, because it acquires +// a lock! +volatile bool sHasScheduledOneLowMemoryNotification = false; +volatile PRIntervalTime sLastLowMemoryNotificationTime; + +// These are function pointers to the functions we wrap in Init(). + +void* (WINAPI* sVirtualAllocOrig)(LPVOID aAddress, SIZE_T aSize, + DWORD aAllocationType, DWORD aProtect); + +void* (WINAPI* sMapViewOfFileOrig)(HANDLE aFileMappingObject, + DWORD aDesiredAccess, DWORD aFileOffsetHigh, + DWORD aFileOffsetLow, SIZE_T aNumBytesToMap); + +HBITMAP(WINAPI* sCreateDIBSectionOrig)(HDC aDC, const BITMAPINFO* aBitmapInfo, + UINT aUsage, VOID** aBits, + HANDLE aSection, DWORD aOffset); + +/** + * Fire a memory pressure event if it's been long enough since the last one we + * fired. + */ +bool +MaybeScheduleMemoryPressureEvent() +{ + // If this interval rolls over, we may fire an extra memory pressure + // event, but that's not a big deal. + PRIntervalTime interval = PR_IntervalNow() - sLastLowMemoryNotificationTime; + if (sHasScheduledOneLowMemoryNotification && + PR_IntervalToMilliseconds(interval) < sLowMemoryNotificationIntervalMS) { + + return false; + } + + // There's a bit of a race condition here, since an interval may be a + // 64-bit number, and 64-bit writes aren't atomic on x86-32. But let's + // not worry about it -- the races only happen when we're already + // experiencing memory pressure and firing notifications, so the worst + // thing that can happen is that we fire two notifications when we + // should have fired only one. + sHasScheduledOneLowMemoryNotification = true; + sLastLowMemoryNotificationTime = PR_IntervalNow(); + + NS_DispatchEventualMemoryPressure(MemPressure_New); + return true; +} + +void +CheckMemAvailable() +{ + if (!sHooksActive) { + return; + } + + MEMORYSTATUSEX stat; + stat.dwLength = sizeof(stat); + bool success = GlobalMemoryStatusEx(&stat); + + if (success) { + // sLowVirtualMemoryThreshold is in MB, but ullAvailVirtual is in bytes. + if (stat.ullAvailVirtual < sLowVirtualMemoryThreshold * 1024 * 1024) { + // If we're running low on virtual memory, unconditionally schedule the + // notification. We'll probably crash if we run out of virtual memory, + // so don't worry about firing this notification too often. + ++sNumLowVirtualMemEvents; + NS_DispatchEventualMemoryPressure(MemPressure_New); + } else if (stat.ullAvailPageFile < sLowCommitSpaceThreshold * 1024 * 1024) { + if (MaybeScheduleMemoryPressureEvent()) { + ++sNumLowCommitSpaceEvents; + } + } else if (stat.ullAvailPhys < sLowPhysicalMemoryThreshold * 1024 * 1024) { + if (MaybeScheduleMemoryPressureEvent()) { + ++sNumLowPhysicalMemEvents; + } + } + } +} + +LPVOID WINAPI +VirtualAllocHook(LPVOID aAddress, SIZE_T aSize, + DWORD aAllocationType, + DWORD aProtect) +{ + // It's tempting to see whether we have enough free virtual address space for + // this allocation and, if we don't, synchronously fire a low-memory + // notification to free some before we allocate. + // + // Unfortunately that doesn't work, principally because code doesn't expect a + // call to malloc could trigger a GC (or call into the other routines which + // are triggered by a low-memory notification). + // + // I think the best we can do here is try to allocate the memory and check + // afterwards how much free virtual address space we have. If we're running + // low, we schedule a low-memory notification to run as soon as possible. + + LPVOID result = sVirtualAllocOrig(aAddress, aSize, aAllocationType, aProtect); + + // Don't call CheckMemAvailable for MEM_RESERVE if we're not tracking low + // virtual memory. Similarly, don't call CheckMemAvailable for MEM_COMMIT if + // we're not tracking low physical memory. + if ((sLowVirtualMemoryThreshold != 0 && aAllocationType & MEM_RESERVE) || + (sLowPhysicalMemoryThreshold != 0 && aAllocationType & MEM_COMMIT)) { + CheckMemAvailable(); + } + + return result; +} + +LPVOID WINAPI +MapViewOfFileHook(HANDLE aFileMappingObject, + DWORD aDesiredAccess, + DWORD aFileOffsetHigh, + DWORD aFileOffsetLow, + SIZE_T aNumBytesToMap) +{ + LPVOID result = sMapViewOfFileOrig(aFileMappingObject, aDesiredAccess, + aFileOffsetHigh, aFileOffsetLow, + aNumBytesToMap); + CheckMemAvailable(); + return result; +} + +HBITMAP WINAPI +CreateDIBSectionHook(HDC aDC, + const BITMAPINFO* aBitmapInfo, + UINT aUsage, + VOID** aBits, + HANDLE aSection, + DWORD aOffset) +{ + // There are a lot of calls to CreateDIBSection, so we make some effort not + // to CheckMemAvailable() for calls to CreateDIBSection which allocate only + // a small amount of memory. + + // If aSection is non-null, CreateDIBSection won't allocate any new memory. + bool doCheck = false; + if (sHooksActive && !aSection && aBitmapInfo) { + uint16_t bitCount = aBitmapInfo->bmiHeader.biBitCount; + if (bitCount == 0) { + // MSDN says bitCount == 0 means that it figures out how many bits each + // pixel gets by examining the corresponding JPEG or PNG data. We'll just + // assume the worst. + bitCount = 32; + } + + // |size| contains the expected allocation size in *bits*. Height may be + // negative (indicating the direction the DIB is drawn in), so we take the + // absolute value. + int64_t size = bitCount * aBitmapInfo->bmiHeader.biWidth * + aBitmapInfo->bmiHeader.biHeight; + if (size < 0) { + size *= -1; + } + + // If we're allocating more than 1MB, check how much memory is left after + // the allocation. + if (size > 1024 * 1024 * 8) { + doCheck = true; + } + } + + HBITMAP result = sCreateDIBSectionOrig(aDC, aBitmapInfo, aUsage, aBits, + aSection, aOffset); + + if (doCheck) { + CheckMemAvailable(); + } + + return result; +} + +static int64_t +LowMemoryEventsVirtualDistinguishedAmount() +{ + return sNumLowVirtualMemEvents; +} + +static int64_t +LowMemoryEventsPhysicalDistinguishedAmount() +{ + return sNumLowPhysicalMemEvents; +} + +class LowEventsReporter final : public nsIMemoryReporter +{ + ~LowEventsReporter() {} + +public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override + { + MOZ_COLLECT_REPORT( + "low-memory-events/virtual", KIND_OTHER, UNITS_COUNT_CUMULATIVE, + LowMemoryEventsVirtualDistinguishedAmount(), +"Number of low-virtual-memory events fired since startup. We fire such an " +"event if we notice there is less than memory.low_virtual_mem_threshold_mb of " +"virtual address space available (if zero, this behavior is disabled). The " +"process will probably crash if it runs out of virtual address space, so " +"this event is dire."); + + MOZ_COLLECT_REPORT( + "low-memory-events/commit-space", KIND_OTHER, UNITS_COUNT_CUMULATIVE, + sNumLowCommitSpaceEvents, +"Number of low-commit-space events fired since startup. We fire such an " +"event if we notice there is less than memory.low_commit_space_threshold_mb of " +"commit space available (if zero, this behavior is disabled). Windows will " +"likely kill the process if it runs out of commit space, so this event is " +"dire."); + + MOZ_COLLECT_REPORT( + "low-memory-events/physical", KIND_OTHER, UNITS_COUNT_CUMULATIVE, + LowMemoryEventsPhysicalDistinguishedAmount(), +"Number of low-physical-memory events fired since startup. We fire such an " +"event if we notice there is less than memory.low_physical_memory_threshold_mb " +"of physical memory available (if zero, this behavior is disabled). The " +"machine will start to page if it runs out of physical memory. This may " +"cause it to run slowly, but it shouldn't cause it to crash."); + + return NS_OK; + } +}; +NS_IMPL_ISUPPORTS(LowEventsReporter, nsIMemoryReporter) + +#endif // defined(_M_IX86) && defined(XP_WIN) + +/** + * This runnable is executed in response to a memory-pressure event; we spin + * the event-loop when receiving the memory-pressure event in the hope that + * other observers will synchronously free some memory that we'll be able to + * purge here. + */ +class nsJemallocFreeDirtyPagesRunnable final : public nsIRunnable +{ + ~nsJemallocFreeDirtyPagesRunnable() {} + +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIRUNNABLE +}; + +NS_IMPL_ISUPPORTS(nsJemallocFreeDirtyPagesRunnable, nsIRunnable) + +NS_IMETHODIMP +nsJemallocFreeDirtyPagesRunnable::Run() +{ + MOZ_ASSERT(NS_IsMainThread()); + +#if defined(MOZ_MEMORY) + jemalloc_free_dirty_pages(); +#endif + + return NS_OK; +} + +/** + * The memory pressure watcher is used for listening to memory-pressure events + * and reacting upon them. We use one instance per process currently only for + * cleaning up dirty unused pages held by jemalloc. + */ +class nsMemoryPressureWatcher final : public nsIObserver +{ + ~nsMemoryPressureWatcher() {} + +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + void Init(); + +private: + static bool sFreeDirtyPages; +}; + +NS_IMPL_ISUPPORTS(nsMemoryPressureWatcher, nsIObserver) + +bool nsMemoryPressureWatcher::sFreeDirtyPages = false; + +/** + * Initialize and subscribe to the memory-pressure events. We subscribe to the + * observer service in this method and not in the constructor because we need + * to hold a strong reference to 'this' before calling the observer service. + */ +void +nsMemoryPressureWatcher::Init() +{ + nsCOMPtr<nsIObserverService> os = services::GetObserverService(); + + if (os) { + os->AddObserver(this, "memory-pressure", /* ownsWeak */ false); + } + + Preferences::AddBoolVarCache(&sFreeDirtyPages, "memory.free_dirty_pages", + false); +} + +/** + * Reacts to all types of memory-pressure events, launches a runnable to + * free dirty pages held by jemalloc. + */ +NS_IMETHODIMP +nsMemoryPressureWatcher::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) +{ + MOZ_ASSERT(!strcmp(aTopic, "memory-pressure"), "Unknown topic"); + + if (sFreeDirtyPages) { + nsCOMPtr<nsIRunnable> runnable = new nsJemallocFreeDirtyPagesRunnable(); + + NS_DispatchToMainThread(runnable); + } + + return NS_OK; +} + +} // namespace + +namespace mozilla { +namespace AvailableMemoryTracker { + +void +Activate() +{ +#if defined(_M_IX86) && defined(XP_WIN) + MOZ_ASSERT(sInitialized); + MOZ_ASSERT(!sHooksActive); + + Preferences::AddUintVarCache(&sLowVirtualMemoryThreshold, + "memory.low_virtual_mem_threshold_mb", 256); + Preferences::AddUintVarCache(&sLowPhysicalMemoryThreshold, + "memory.low_physical_memory_threshold_mb", 0); + Preferences::AddUintVarCache(&sLowCommitSpaceThreshold, + "memory.low_commit_space_threshold_mb", 256); + Preferences::AddUintVarCache(&sLowMemoryNotificationIntervalMS, + "memory.low_memory_notification_interval_ms", + 10000); + + RegisterStrongMemoryReporter(new LowEventsReporter()); + RegisterLowMemoryEventsVirtualDistinguishedAmount( + LowMemoryEventsVirtualDistinguishedAmount); + RegisterLowMemoryEventsPhysicalDistinguishedAmount( + LowMemoryEventsPhysicalDistinguishedAmount); + sHooksActive = true; +#endif + + // This object is held alive by the observer service. + RefPtr<nsMemoryPressureWatcher> watcher = new nsMemoryPressureWatcher(); + watcher->Init(); +} + +void +Init() +{ + // Do nothing on x86-64, because nsWindowsDllInterceptor is not thread-safe + // on 64-bit. (On 32-bit, it's probably thread-safe.) Even if we run Init() + // before any other of our threads are running, another process may have + // started a remote thread which could call VirtualAlloc! + // + // Moreover, the benefit of this code is less clear when we're a 64-bit + // process, because we aren't going to run out of virtual memory, and the + // system is likely to have a fair bit of physical memory. + +#if defined(_M_IX86) && defined(XP_WIN) + // Don't register the hooks if we're a build instrumented for PGO: If we're + // an instrumented build, the compiler adds function calls all over the place + // which may call VirtualAlloc; this makes it hard to prevent + // VirtualAllocHook from reentering itself. + if (!PR_GetEnv("MOZ_PGO_INSTRUMENTED")) { + sKernel32Intercept.Init("Kernel32.dll"); + sKernel32Intercept.AddHook("VirtualAlloc", + reinterpret_cast<intptr_t>(VirtualAllocHook), + reinterpret_cast<void**>(&sVirtualAllocOrig)); + sKernel32Intercept.AddHook("MapViewOfFile", + reinterpret_cast<intptr_t>(MapViewOfFileHook), + reinterpret_cast<void**>(&sMapViewOfFileOrig)); + + sGdi32Intercept.Init("Gdi32.dll"); + sGdi32Intercept.AddHook("CreateDIBSection", + reinterpret_cast<intptr_t>(CreateDIBSectionHook), + reinterpret_cast<void**>(&sCreateDIBSectionOrig)); + } + + sInitialized = true; +#endif +} + +} // namespace AvailableMemoryTracker +} // namespace mozilla diff --git a/xpcom/base/AvailableMemoryTracker.h b/xpcom/base/AvailableMemoryTracker.h new file mode 100644 index 000000000..33572f9c7 --- /dev/null +++ b/xpcom/base/AvailableMemoryTracker.h @@ -0,0 +1,30 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_AvailableMemoryTracker_h +#define mozilla_AvailableMemoryTracker_h + +namespace mozilla { +namespace AvailableMemoryTracker { + +// The AvailableMemoryTracker launches a memory pressure watcher on all +// platforms to react to low-memory situations and on Windows it implements +// the full functionality used to monitor how much memory is available. +// +// Init() must be called before any other threads have started, because it +// modifies the in-memory implementations of some DLL functions in +// non-thread-safe ways. +// +// The hooks don't do anything until Activate() is called. It's an error to +// call Activate() without first calling Init(). + +void Init(); +void Activate(); + +} // namespace AvailableMemoryTracker +} // namespace mozilla + +#endif // ifndef mozilla_AvailableMemoryTracker_h diff --git a/xpcom/base/ClearOnShutdown.cpp b/xpcom/base/ClearOnShutdown.cpp new file mode 100644 index 000000000..bfb3142bc --- /dev/null +++ b/xpcom/base/ClearOnShutdown.cpp @@ -0,0 +1,45 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/ClearOnShutdown.h" + +namespace mozilla { +namespace ClearOnShutdown_Internal { + +Array<StaticAutoPtr<ShutdownList>, + static_cast<size_t>(ShutdownPhase::ShutdownPhase_Length)> sShutdownObservers; +ShutdownPhase sCurrentShutdownPhase = ShutdownPhase::NotInShutdown; + +} // namespace ClearOnShutdown_Internal + +// Called when XPCOM is shutting down, after all shutdown notifications have +// been sent and after all threads' event loops have been purged. +void +KillClearOnShutdown(ShutdownPhase aPhase) +{ + using namespace ClearOnShutdown_Internal; + + MOZ_ASSERT(NS_IsMainThread()); + // Shutdown only goes one direction... + MOZ_ASSERT(static_cast<size_t>(sCurrentShutdownPhase) < static_cast<size_t>(aPhase)); + + // It's impossible to add an entry for a "past" phase; this is blocked in + // ClearOnShutdown, but clear them out anyways in case there are phases + // that weren't passed to KillClearOnShutdown. + for (size_t phase = static_cast<size_t>(ShutdownPhase::First); + phase <= static_cast<size_t>(aPhase); + phase++) { + if (sShutdownObservers[static_cast<size_t>(phase)]) { + while (ShutdownObserver* observer = sShutdownObservers[static_cast<size_t>(phase)]->popFirst()) { + observer->Shutdown(); + delete observer; + } + sShutdownObservers[static_cast<size_t>(phase)] = nullptr; + } + } +} + +} // namespace mozilla diff --git a/xpcom/base/ClearOnShutdown.h b/xpcom/base/ClearOnShutdown.h new file mode 100644 index 000000000..5c39c281c --- /dev/null +++ b/xpcom/base/ClearOnShutdown.h @@ -0,0 +1,124 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_ClearOnShutdown_h +#define mozilla_ClearOnShutdown_h + +#include "mozilla/LinkedList.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/Array.h" +#include "MainThreadUtils.h" + +/* + * This header exports one public method in the mozilla namespace: + * + * template<class SmartPtr> + * void ClearOnShutdown(SmartPtr *aPtr, aPhase=ShutdownPhase::ShutdownFinal) + * + * This function takes a pointer to a smart pointer and nulls the smart pointer + * on shutdown (and a particular phase of shutdown as needed). If a phase + * is specified, the ptr will be cleared at the start of that phase. Also, + * if a phase has already occurred when ClearOnShutdown() is called it will + * cause a MOZ_ASSERT. In case a phase is not explicitly cleared we will + * clear it on the next phase that occurs. + * + * This is useful if you have a global smart pointer object which you don't + * want to "leak" on shutdown. + * + * Although ClearOnShutdown will work with any smart pointer (i.e., nsCOMPtr, + * nsRefPtr, nsAutoPtr, StaticRefPtr, and StaticAutoPtr), you probably want to + * use it only with StaticRefPtr and StaticAutoPtr. There is no way to undo a + * call to ClearOnShutdown, so you can call it only on smart pointers which you + * know will live until the program shuts down. In practice, these are likely + * global variables, which should be Static{Ref,Auto}Ptr. + * + * ClearOnShutdown is currently main-thread only because we don't want to + * accidentally free an object from a different thread than the one it was + * created on. + */ + +namespace mozilla { + +// Must be contiguous starting at 0 +enum class ShutdownPhase { + NotInShutdown = 0, + WillShutdown, + Shutdown, + ShutdownThreads, + ShutdownLoaders, + ShutdownFinal, + ShutdownPhase_Length, // never pass this value + First = WillShutdown, // for iteration + Last = ShutdownFinal +}; + +namespace ClearOnShutdown_Internal { + +class ShutdownObserver : public LinkedListElement<ShutdownObserver> +{ +public: + virtual void Shutdown() = 0; + virtual ~ShutdownObserver() + { + } +}; + +template<class SmartPtr> +class PointerClearer : public ShutdownObserver +{ +public: + explicit PointerClearer(SmartPtr* aPtr) + : mPtr(aPtr) + { + } + + virtual void Shutdown() override + { + if (mPtr) { + *mPtr = nullptr; + } + } + +private: + SmartPtr* mPtr; +}; + +typedef LinkedList<ShutdownObserver> ShutdownList; +extern Array<StaticAutoPtr<ShutdownList>, + static_cast<size_t>(ShutdownPhase::ShutdownPhase_Length)> sShutdownObservers; +extern ShutdownPhase sCurrentShutdownPhase; + +} // namespace ClearOnShutdown_Internal + +template<class SmartPtr> +inline void +ClearOnShutdown(SmartPtr* aPtr, ShutdownPhase aPhase = ShutdownPhase::ShutdownFinal) +{ + using namespace ClearOnShutdown_Internal; + + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aPhase != ShutdownPhase::ShutdownPhase_Length); + + // Adding a ClearOnShutdown for a "past" phase is an error. + if (!(static_cast<size_t>(sCurrentShutdownPhase) < static_cast<size_t>(aPhase))) { + MOZ_ASSERT(false, "ClearOnShutdown for phase that already was cleared"); + *aPtr = nullptr; + return; + } + + if (!(sShutdownObservers[static_cast<size_t>(aPhase)])) { + sShutdownObservers[static_cast<size_t>(aPhase)] = new ShutdownList(); + } + sShutdownObservers[static_cast<size_t>(aPhase)]->insertBack(new PointerClearer<SmartPtr>(aPtr)); +} + +// Called when XPCOM is shutting down, after all shutdown notifications have +// been sent and after all threads' event loops have been purged. +void KillClearOnShutdown(ShutdownPhase aPhase); + +} // namespace mozilla + +#endif diff --git a/xpcom/base/CodeAddressService.h b/xpcom/base/CodeAddressService.h new file mode 100644 index 000000000..7f91f93a6 --- /dev/null +++ b/xpcom/base/CodeAddressService.h @@ -0,0 +1,198 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef CodeAddressService_h__ +#define CodeAddressService_h__ + +#include "mozilla/Assertions.h" +#include "mozilla/HashFunctions.h" +#include "mozilla/IntegerPrintfMacros.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Types.h" + +#include "mozilla/StackWalk.h" + +namespace mozilla { + +// This class is used to print details about code locations. +// +// |StringTable| must implement an Intern() method that returns an interned +// copy of the string that was passed in, as well as a standard +// SizeOfExcludingThis() method. +// +// |StringAlloc| must implement |copy| and |free|. |copy| copies a string, +// while |free| is used to free strings created by |copy|. +// +// |DescribeCodeAddressLock| is needed when the callers may be holding a lock +// used by MozDescribeCodeAddress. |DescribeCodeAddressLock| must implement +// static methods IsLocked(), Unlock() and Lock(). +template <class StringTable, + class StringAlloc, + class DescribeCodeAddressLock> +class CodeAddressService +{ + // GetLocation() is the key function in this class. It's basically a wrapper + // around MozDescribeCodeAddress. + // + // However, MozDescribeCodeAddress is very slow on some platforms, and we + // have lots of repeated (i.e. same PC) calls to it. So we do some caching + // of results. Each cached result includes two strings (|mFunction| and + // |mLibrary|), so we also optimize them for space in the following ways. + // + // - The number of distinct library names is small, e.g. a few dozen. There + // is lots of repetition, especially of libxul. So we intern them in their + // own table, which saves space over duplicating them for each cache entry. + // + // - The number of distinct function names is much higher, so we duplicate + // them in each cache entry. That's more space-efficient than interning + // because entries containing single-occurrence function names are quickly + // overwritten, and their copies released. In addition, empty function + // names are common, so we use nullptr to represent them compactly. + + StringTable mLibraryStrings; + + struct Entry + { + const void* mPc; + char* mFunction; // owned by the Entry; may be null + const char* mLibrary; // owned by mLibraryStrings; never null + // in a non-empty entry is in use + ptrdiff_t mLOffset; + char* mFileName; // owned by the Entry; may be null + uint32_t mLineNo:31; + uint32_t mInUse:1; // is the entry used? + + Entry() + : mPc(0), mFunction(nullptr), mLibrary(nullptr), mLOffset(0), + mFileName(nullptr), mLineNo(0), mInUse(0) + {} + + ~Entry() + { + // We don't free mLibrary because it's externally owned. + StringAlloc::free(mFunction); + StringAlloc::free(mFileName); + } + + void Replace(const void* aPc, const char* aFunction, + const char* aLibrary, ptrdiff_t aLOffset, + const char* aFileName, unsigned long aLineNo) + { + mPc = aPc; + + // Convert "" to nullptr. Otherwise, make a copy of the name. + StringAlloc::free(mFunction); + mFunction = !aFunction[0] ? nullptr : StringAlloc::copy(aFunction); + StringAlloc::free(mFileName); + mFileName = !aFileName[0] ? nullptr : StringAlloc::copy(aFileName); + + mLibrary = aLibrary; + mLOffset = aLOffset; + mLineNo = aLineNo; + + mInUse = 1; + } + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const + { + // Don't measure mLibrary because it's externally owned. + size_t n = 0; + n += aMallocSizeOf(mFunction); + n += aMallocSizeOf(mFileName); + return n; + } + }; + + // A direct-mapped cache. When doing dmd::Analyze() just after starting + // desktop Firefox (which is similar to analyzing after a longer-running + // session, thanks to the limit on how many records we print), a cache with + // 2^24 entries (which approximates an infinite-entry cache) has a ~91% hit + // rate. A cache with 2^12 entries has a ~83% hit rate, and takes up ~85 KiB + // (on 32-bit platforms) or ~150 KiB (on 64-bit platforms). + static const size_t kNumEntries = 1 << 12; + static const size_t kMask = kNumEntries - 1; + Entry mEntries[kNumEntries]; + + size_t mNumCacheHits; + size_t mNumCacheMisses; + +public: + CodeAddressService() + : mEntries(), mNumCacheHits(0), mNumCacheMisses(0) + { + } + + void GetLocation(uint32_t aFrameNumber, const void* aPc, char* aBuf, + size_t aBufLen) + { + MOZ_ASSERT(DescribeCodeAddressLock::IsLocked()); + + uint32_t index = HashGeneric(aPc) & kMask; + MOZ_ASSERT(index < kNumEntries); + Entry& entry = mEntries[index]; + + if (!entry.mInUse || entry.mPc != aPc) { + mNumCacheMisses++; + + // MozDescribeCodeAddress can (on Linux) acquire a lock inside + // the shared library loader. Another thread might call malloc + // while holding that lock (when loading a shared library). So + // we have to exit the lock around this call. For details, see + // https://bugzilla.mozilla.org/show_bug.cgi?id=363334#c3 + MozCodeAddressDetails details; + { + DescribeCodeAddressLock::Unlock(); + (void)MozDescribeCodeAddress(const_cast<void*>(aPc), &details); + DescribeCodeAddressLock::Lock(); + } + + const char* library = mLibraryStrings.Intern(details.library); + entry.Replace(aPc, details.function, library, details.loffset, + details.filename, details.lineno); + + } else { + mNumCacheHits++; + } + + MOZ_ASSERT(entry.mPc == aPc); + + MozFormatCodeAddress(aBuf, aBufLen, aFrameNumber, entry.mPc, + entry.mFunction, entry.mLibrary, entry.mLOffset, + entry.mFileName, entry.mLineNo); + } + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const + { + size_t n = aMallocSizeOf(this); + for (uint32_t i = 0; i < kNumEntries; i++) { + n += mEntries[i].SizeOfExcludingThis(aMallocSizeOf); + } + + n += mLibraryStrings.SizeOfExcludingThis(aMallocSizeOf); + + return n; + } + + size_t CacheCapacity() const { return kNumEntries; } + + size_t CacheCount() const + { + size_t n = 0; + for (size_t i = 0; i < kNumEntries; i++) { + if (mEntries[i].mInUse) { + n++; + } + } + return n; + } + + size_t NumCacheHits() const { return mNumCacheHits; } + size_t NumCacheMisses() const { return mNumCacheMisses; } +}; + +} // namespace mozilla + +#endif // CodeAddressService_h__ diff --git a/xpcom/base/CountingAllocatorBase.h b/xpcom/base/CountingAllocatorBase.h new file mode 100644 index 000000000..fb4d2ffe8 --- /dev/null +++ b/xpcom/base/CountingAllocatorBase.h @@ -0,0 +1,140 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef CountingAllocatorBase_h +#define CountingAllocatorBase_h + +#include "mozilla/Assertions.h" +#include "mozilla/Atomics.h" +#include "nsIMemoryReporter.h" + +namespace mozilla { + +// This CRTP class handles several details of wrapping allocators and should +// be preferred to manually counting with MOZ_DEFINE_MALLOC_SIZE_OF_ON_ALLOC +// and MOZ_DEFINE_MALLOC_SIZE_OF_ON_FREE. The typical use is in a memory +// reporter for a particular third party library: +// +// class MyMemoryReporter : public CountingAllocatorBase<MyMemoryReporter> +// { +// ... +// NS_IMETHOD +// CollectReports(nsIHandleReportCallback* aHandleReport, +// nsISupports* aData, bool aAnonymize) override +// { +// MOZ_COLLECT_REPORT( +// "explicit/path/to/somewhere", KIND_HEAP, UNITS_BYTES, +// MemoryAllocated(), +// "A description of what we are reporting."); +// +// return NS_OK; +// } +// }; +// +// ...somewhere later in the code... +// SetThirdPartyMemoryFunctions(MyMemoryReporter::CountingAlloc, +// MyMemoryReporter::CountingFree); +template<typename T> +class CountingAllocatorBase +{ +public: + CountingAllocatorBase() + { +#ifdef DEBUG + // There must be only one instance of this class, due to |sAmount| being + // static. + static bool hasRun = false; + MOZ_ASSERT(!hasRun); + hasRun = true; +#endif + } + + static size_t + MemoryAllocated() + { + return sAmount; + } + + static void* + CountingMalloc(size_t size) + { + void* p = malloc(size); + sAmount += MallocSizeOfOnAlloc(p); + return p; + } + + static void* + CountingCalloc(size_t nmemb, size_t size) + { + void* p = calloc(nmemb, size); + sAmount += MallocSizeOfOnAlloc(p); + return p; + } + + static void* + CountingRealloc(void* p, size_t size) + { + size_t oldsize = MallocSizeOfOnFree(p); + void *pnew = realloc(p, size); + if (pnew) { + size_t newsize = MallocSizeOfOnAlloc(pnew); + sAmount += newsize - oldsize; + } else if (size == 0) { + // We asked for a 0-sized (re)allocation of some existing pointer + // and received NULL in return. 0-sized allocations are permitted + // to either return NULL or to allocate a unique object per call (!). + // For a malloc implementation that chooses the second strategy, + // that allocation may fail (unlikely, but possible). + // + // Given a NULL return value and an allocation size of 0, then, we + // don't know if that means the original pointer was freed or if + // the allocation of the unique object failed. If the original + // pointer was freed, then we have nothing to do here. If the + // allocation of the unique object failed, the original pointer is + // still valid and we ought to undo the decrement from above. + // However, we have no way of knowing how the underlying realloc + // implementation is behaving. Assuming that the original pointer + // was freed is the safest course of action. We do, however, need + // to note that we freed memory. + sAmount -= oldsize; + } else { + // realloc failed. The amount allocated hasn't changed. + } + return pnew; + } + + // Some library code expects that realloc(x, 0) will free x, which is not + // the behavior of the version of jemalloc we're using, so this wrapped + // version of realloc is needed. + static void* + CountingFreeingRealloc(void* p, size_t size) + { + if (size == 0) { + CountingFree(p); + return nullptr; + } + return CountingRealloc(p, size); + } + + static void + CountingFree(void* p) + { + sAmount -= MallocSizeOfOnFree(p); + free(p); + } + +private: + // |sAmount| can be (implicitly) accessed by multiple threads, so it + // must be thread-safe. + static Atomic<size_t> sAmount; + + MOZ_DEFINE_MALLOC_SIZE_OF_ON_ALLOC(MallocSizeOfOnAlloc) + MOZ_DEFINE_MALLOC_SIZE_OF_ON_FREE(MallocSizeOfOnFree) +}; + +} // namespace mozilla + +#endif // CountingAllocatorBase_h diff --git a/xpcom/base/CycleCollectedJSContext.cpp b/xpcom/base/CycleCollectedJSContext.cpp new file mode 100644 index 000000000..87e123078 --- /dev/null +++ b/xpcom/base/CycleCollectedJSContext.cpp @@ -0,0 +1,1717 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// We're dividing JS objects into 3 categories: +// +// 1. "real" roots, held by the JS engine itself or rooted through the root +// and lock JS APIs. Roots from this category are considered black in the +// cycle collector, any cycle they participate in is uncollectable. +// +// 2. certain roots held by C++ objects that are guaranteed to be alive. +// Roots from this category are considered black in the cycle collector, +// and any cycle they participate in is uncollectable. These roots are +// traced from TraceNativeBlackRoots. +// +// 3. all other roots held by C++ objects that participate in cycle +// collection, held by us (see TraceNativeGrayRoots). Roots from this +// category are considered grey in the cycle collector; whether or not +// they are collected depends on the objects that hold them. +// +// Note that if a root is in multiple categories the fact that it is in +// category 1 or 2 that takes precedence, so it will be considered black. +// +// During garbage collection we switch to an additional mark color (gray) +// when tracing inside TraceNativeGrayRoots. This allows us to walk those +// roots later on and add all objects reachable only from them to the +// cycle collector. +// +// Phases: +// +// 1. marking of the roots in category 1 by having the JS GC do its marking +// 2. marking of the roots in category 2 by having the JS GC call us back +// (via JS_SetExtraGCRootsTracer) and running TraceNativeBlackRoots +// 3. marking of the roots in category 3 by TraceNativeGrayRoots using an +// additional color (gray). +// 4. end of GC, GC can sweep its heap +// +// At some later point, when the cycle collector runs: +// +// 5. walk gray objects and add them to the cycle collector, cycle collect +// +// JS objects that are part of cycles the cycle collector breaks will be +// collected by the next JS GC. +// +// If WantAllTraces() is false the cycle collector will not traverse roots +// from category 1 or any JS objects held by them. Any JS objects they hold +// will already be marked by the JS GC and will thus be colored black +// themselves. Any C++ objects they hold will have a missing (untraversed) +// edge from the JS object to the C++ object and so it will be marked black +// too. This decreases the number of objects that the cycle collector has to +// deal with. +// To improve debugging, if WantAllTraces() is true all JS objects are +// traversed. + +#include "mozilla/CycleCollectedJSContext.h" +#include <algorithm> +#include "mozilla/ArrayUtils.h" +#include "mozilla/AutoRestore.h" +#include "mozilla/Move.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Sprintf.h" +#include "mozilla/Telemetry.h" +#include "mozilla/TimelineConsumers.h" +#include "mozilla/TimelineMarker.h" +#include "mozilla/Unused.h" +#include "mozilla/DebuggerOnGCRunnable.h" +#include "mozilla/dom/DOMJSClass.h" +#include "mozilla/dom/ProfileTimelineMarkerBinding.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/PromiseBinding.h" +#include "mozilla/dom/PromiseDebugging.h" +#include "mozilla/dom/ScriptSettings.h" +#include "jsprf.h" +#include "js/Debug.h" +#include "nsContentUtils.h" +#include "nsCycleCollectionNoteRootCallback.h" +#include "nsCycleCollectionParticipant.h" +#include "nsCycleCollector.h" +#include "nsDOMJSUtils.h" +#include "nsJSUtils.h" +#include "nsWrapperCache.h" + +#ifdef MOZ_CRASHREPORTER +#include "nsExceptionHandler.h" +#endif + +#include "nsIException.h" +#include "nsIPlatformInfo.h" +#include "nsThread.h" +#include "nsThreadUtils.h" +#include "xpcpublic.h" + +using namespace mozilla; +using namespace mozilla::dom; + +namespace mozilla { + +struct DeferredFinalizeFunctionHolder +{ + DeferredFinalizeFunction run; + void* data; +}; + +class IncrementalFinalizeRunnable : public Runnable +{ + typedef AutoTArray<DeferredFinalizeFunctionHolder, 16> DeferredFinalizeArray; + typedef CycleCollectedJSContext::DeferredFinalizerTable DeferredFinalizerTable; + + CycleCollectedJSContext* mContext; + DeferredFinalizeArray mDeferredFinalizeFunctions; + uint32_t mFinalizeFunctionToRun; + bool mReleasing; + + static const PRTime SliceMillis = 5; /* ms */ + +public: + IncrementalFinalizeRunnable(CycleCollectedJSContext* aCx, + DeferredFinalizerTable& aFinalizerTable); + virtual ~IncrementalFinalizeRunnable(); + + void ReleaseNow(bool aLimited); + + NS_DECL_NSIRUNNABLE +}; + +} // namespace mozilla + +struct NoteWeakMapChildrenTracer : public JS::CallbackTracer +{ + NoteWeakMapChildrenTracer(JSContext* aCx, + nsCycleCollectionNoteRootCallback& aCb) + : JS::CallbackTracer(aCx), mCb(aCb), mTracedAny(false), mMap(nullptr), + mKey(nullptr), mKeyDelegate(nullptr) + { + } + void onChild(const JS::GCCellPtr& aThing) override; + nsCycleCollectionNoteRootCallback& mCb; + bool mTracedAny; + JSObject* mMap; + JS::GCCellPtr mKey; + JSObject* mKeyDelegate; +}; + +void +NoteWeakMapChildrenTracer::onChild(const JS::GCCellPtr& aThing) +{ + if (aThing.is<JSString>()) { + return; + } + + if (!JS::GCThingIsMarkedGray(aThing) && !mCb.WantAllTraces()) { + return; + } + + if (AddToCCKind(aThing.kind())) { + mCb.NoteWeakMapping(mMap, mKey, mKeyDelegate, aThing); + mTracedAny = true; + } else { + JS::TraceChildren(this, aThing); + } +} + +struct NoteWeakMapsTracer : public js::WeakMapTracer +{ + NoteWeakMapsTracer(JSContext* aCx, nsCycleCollectionNoteRootCallback& aCccb) + : js::WeakMapTracer(aCx), mCb(aCccb), mChildTracer(aCx, aCccb) + { + } + void trace(JSObject* aMap, JS::GCCellPtr aKey, JS::GCCellPtr aValue) override; + nsCycleCollectionNoteRootCallback& mCb; + NoteWeakMapChildrenTracer mChildTracer; +}; + +void +NoteWeakMapsTracer::trace(JSObject* aMap, JS::GCCellPtr aKey, + JS::GCCellPtr aValue) +{ + // If nothing that could be held alive by this entry is marked gray, return. + if ((!aKey || !JS::GCThingIsMarkedGray(aKey)) && + MOZ_LIKELY(!mCb.WantAllTraces())) { + if (!aValue || !JS::GCThingIsMarkedGray(aValue) || aValue.is<JSString>()) { + return; + } + } + + // The cycle collector can only properly reason about weak maps if it can + // reason about the liveness of their keys, which in turn requires that + // the key can be represented in the cycle collector graph. All existing + // uses of weak maps use either objects or scripts as keys, which are okay. + MOZ_ASSERT(AddToCCKind(aKey.kind())); + + // As an emergency fallback for non-debug builds, if the key is not + // representable in the cycle collector graph, we treat it as marked. This + // can cause leaks, but is preferable to ignoring the binding, which could + // cause the cycle collector to free live objects. + if (!AddToCCKind(aKey.kind())) { + aKey = nullptr; + } + + JSObject* kdelegate = nullptr; + if (aKey.is<JSObject>()) { + kdelegate = js::GetWeakmapKeyDelegate(&aKey.as<JSObject>()); + } + + if (AddToCCKind(aValue.kind())) { + mCb.NoteWeakMapping(aMap, aKey, kdelegate, aValue); + } else { + mChildTracer.mTracedAny = false; + mChildTracer.mMap = aMap; + mChildTracer.mKey = aKey; + mChildTracer.mKeyDelegate = kdelegate; + + if (!aValue.is<JSString>()) { + JS::TraceChildren(&mChildTracer, aValue); + } + + // The delegate could hold alive the key, so report something to the CC + // if we haven't already. + if (!mChildTracer.mTracedAny && + aKey && JS::GCThingIsMarkedGray(aKey) && kdelegate) { + mCb.NoteWeakMapping(aMap, aKey, kdelegate, nullptr); + } + } +} + +// This is based on the logic in FixWeakMappingGrayBitsTracer::trace. +struct FixWeakMappingGrayBitsTracer : public js::WeakMapTracer +{ + explicit FixWeakMappingGrayBitsTracer(JSContext* aCx) + : js::WeakMapTracer(aCx) + { + } + + void + FixAll() + { + do { + mAnyMarked = false; + js::TraceWeakMaps(this); + } while (mAnyMarked); + } + + void trace(JSObject* aMap, JS::GCCellPtr aKey, JS::GCCellPtr aValue) override + { + // If nothing that could be held alive by this entry is marked gray, return. + bool keyMightNeedMarking = aKey && JS::GCThingIsMarkedGray(aKey); + bool valueMightNeedMarking = aValue && JS::GCThingIsMarkedGray(aValue) && + aValue.kind() != JS::TraceKind::String; + if (!keyMightNeedMarking && !valueMightNeedMarking) { + return; + } + + if (!AddToCCKind(aKey.kind())) { + aKey = nullptr; + } + + if (keyMightNeedMarking && aKey.is<JSObject>()) { + JSObject* kdelegate = js::GetWeakmapKeyDelegate(&aKey.as<JSObject>()); + if (kdelegate && !JS::ObjectIsMarkedGray(kdelegate) && + (!aMap || !JS::ObjectIsMarkedGray(aMap))) + { + if (JS::UnmarkGrayGCThingRecursively(aKey)) { + mAnyMarked = true; + } + } + } + + if (aValue && JS::GCThingIsMarkedGray(aValue) && + (!aKey || !JS::GCThingIsMarkedGray(aKey)) && + (!aMap || !JS::ObjectIsMarkedGray(aMap)) && + aValue.kind() != JS::TraceKind::Shape) { + if (JS::UnmarkGrayGCThingRecursively(aValue)) { + mAnyMarked = true; + } + } + } + + MOZ_INIT_OUTSIDE_CTOR bool mAnyMarked; +}; + +static void +CheckParticipatesInCycleCollection(JS::GCCellPtr aThing, const char* aName, + void* aClosure) +{ + bool* cycleCollectionEnabled = static_cast<bool*>(aClosure); + + if (*cycleCollectionEnabled) { + return; + } + + if (AddToCCKind(aThing.kind()) && JS::GCThingIsMarkedGray(aThing)) { + *cycleCollectionEnabled = true; + } +} + +NS_IMETHODIMP +JSGCThingParticipant::Traverse(void* aPtr, + nsCycleCollectionTraversalCallback& aCb) +{ + auto runtime = reinterpret_cast<CycleCollectedJSContext*>( + reinterpret_cast<char*>(this) - offsetof(CycleCollectedJSContext, + mGCThingCycleCollectorGlobal)); + + JS::GCCellPtr cellPtr(aPtr, JS::GCThingTraceKind(aPtr)); + runtime->TraverseGCThing(CycleCollectedJSContext::TRAVERSE_FULL, cellPtr, aCb); + return NS_OK; +} + +// NB: This is only used to initialize the participant in +// CycleCollectedJSContext. It should never be used directly. +static JSGCThingParticipant sGCThingCycleCollectorGlobal; + +NS_IMETHODIMP +JSZoneParticipant::Traverse(void* aPtr, nsCycleCollectionTraversalCallback& aCb) +{ + auto runtime = reinterpret_cast<CycleCollectedJSContext*>( + reinterpret_cast<char*>(this) - offsetof(CycleCollectedJSContext, + mJSZoneCycleCollectorGlobal)); + + MOZ_ASSERT(!aCb.WantAllTraces()); + JS::Zone* zone = static_cast<JS::Zone*>(aPtr); + + runtime->TraverseZone(zone, aCb); + return NS_OK; +} + +struct TraversalTracer : public JS::CallbackTracer +{ + TraversalTracer(JSContext* aCx, nsCycleCollectionTraversalCallback& aCb) + : JS::CallbackTracer(aCx, DoNotTraceWeakMaps), mCb(aCb) + { + } + void onChild(const JS::GCCellPtr& aThing) override; + nsCycleCollectionTraversalCallback& mCb; +}; + +void +TraversalTracer::onChild(const JS::GCCellPtr& aThing) +{ + // Don't traverse non-gray objects, unless we want all traces. + if (!JS::GCThingIsMarkedGray(aThing) && !mCb.WantAllTraces()) { + return; + } + + /* + * This function needs to be careful to avoid stack overflow. Normally, when + * AddToCCKind is true, the recursion terminates immediately as we just add + * |thing| to the CC graph. So overflow is only possible when there are long + * or cyclic chains of non-AddToCCKind GC things. Places where this can occur + * use special APIs to handle such chains iteratively. + */ + if (AddToCCKind(aThing.kind())) { + if (MOZ_UNLIKELY(mCb.WantDebugInfo())) { + char buffer[200]; + getTracingEdgeName(buffer, sizeof(buffer)); + mCb.NoteNextEdgeName(buffer); + } + mCb.NoteJSChild(aThing); + } else if (aThing.is<js::Shape>()) { + // The maximum depth of traversal when tracing a Shape is unbounded, due to + // the parent pointers on the shape. + JS_TraceShapeCycleCollectorChildren(this, aThing); + } else if (aThing.is<js::ObjectGroup>()) { + // The maximum depth of traversal when tracing an ObjectGroup is unbounded, + // due to information attached to the groups which can lead other groups to + // be traced. + JS_TraceObjectGroupCycleCollectorChildren(this, aThing); + } else if (!aThing.is<JSString>()) { + JS::TraceChildren(this, aThing); + } +} + +static void +NoteJSChildGrayWrapperShim(void* aData, JS::GCCellPtr aThing) +{ + TraversalTracer* trc = static_cast<TraversalTracer*>(aData); + trc->onChild(aThing); +} + +/* + * The cycle collection participant for a Zone is intended to produce the same + * results as if all of the gray GCthings in a zone were merged into a single node, + * except for self-edges. This avoids the overhead of representing all of the GCthings in + * the zone in the cycle collector graph, which should be much faster if many of + * the GCthings in the zone are gray. + * + * Zone merging should not always be used, because it is a conservative + * approximation of the true cycle collector graph that can incorrectly identify some + * garbage objects as being live. For instance, consider two cycles that pass through a + * zone, where one is garbage and the other is live. If we merge the entire + * zone, the cycle collector will think that both are alive. + * + * We don't have to worry about losing track of a garbage cycle, because any such garbage + * cycle incorrectly identified as live must contain at least one C++ to JS edge, and + * XPConnect will always add the C++ object to the CC graph. (This is in contrast to pure + * C++ garbage cycles, which must always be properly identified, because we clear the + * purple buffer during every CC, which may contain the last reference to a garbage + * cycle.) + */ + +// NB: This is only used to initialize the participant in +// CycleCollectedJSContext. It should never be used directly. +static const JSZoneParticipant sJSZoneCycleCollectorGlobal; + +static +void JSObjectsTenuredCb(JSContext* aContext, void* aData) +{ + static_cast<CycleCollectedJSContext*>(aData)->JSObjectsTenured(); +} + +bool +mozilla::GetBuildId(JS::BuildIdCharVector* aBuildID) +{ + nsCOMPtr<nsIPlatformInfo> info = do_GetService("@mozilla.org/xre/app-info;1"); + if (!info) { + return false; + } + + nsCString buildID; + nsresult rv = info->GetPlatformBuildID(buildID); + NS_ENSURE_SUCCESS(rv, false); + + if (!aBuildID->resize(buildID.Length())) { + return false; + } + + for (size_t i = 0; i < buildID.Length(); i++) { + (*aBuildID)[i] = buildID[i]; + } + + return true; +} + +CycleCollectedJSContext::CycleCollectedJSContext() + : mGCThingCycleCollectorGlobal(sGCThingCycleCollectorGlobal) + , mJSZoneCycleCollectorGlobal(sJSZoneCycleCollectorGlobal) + , mJSContext(nullptr) + , mPrevGCSliceCallback(nullptr) + , mPrevGCNurseryCollectionCallback(nullptr) + , mJSHolders(256) + , mDoingStableStates(false) + , mDisableMicroTaskCheckpoint(false) + , mOutOfMemoryState(OOMState::OK) + , mLargeAllocationFailureState(OOMState::OK) +{ + nsCOMPtr<nsIThread> thread = do_GetCurrentThread(); + mOwningThread = thread.forget().downcast<nsThread>().take(); + MOZ_RELEASE_ASSERT(mOwningThread); +} + +CycleCollectedJSContext::~CycleCollectedJSContext() +{ + // If the allocation failed, here we are. + if (!mJSContext) { + return; + } + + MOZ_ASSERT(!mDeferredFinalizerTable.Count()); + + // Last chance to process any events. + ProcessMetastableStateQueue(mBaseRecursionDepth); + MOZ_ASSERT(mMetastableStateEvents.IsEmpty()); + + ProcessStableStateQueue(); + MOZ_ASSERT(mStableStateEvents.IsEmpty()); + + // Clear mPendingException first, since it might be cycle collected. + mPendingException = nullptr; + + MOZ_ASSERT(mDebuggerPromiseMicroTaskQueue.empty()); + MOZ_ASSERT(mPromiseMicroTaskQueue.empty()); + +#ifdef SPIDERMONKEY_PROMISE + mUncaughtRejections.reset(); + mConsumedRejections.reset(); +#endif // SPIDERMONKEY_PROMISE + + JS_DestroyContext(mJSContext); + mJSContext = nullptr; + nsCycleCollector_forgetJSContext(); + + mozilla::dom::DestroyScriptSettings(); + + mOwningThread->SetScriptObserver(nullptr); + NS_RELEASE(mOwningThread); +} + +static void +MozCrashWarningReporter(JSContext*, JSErrorReport*) +{ + MOZ_CRASH("Why is someone touching JSAPI without an AutoJSAPI?"); +} + +nsresult +CycleCollectedJSContext::Initialize(JSContext* aParentContext, + uint32_t aMaxBytes, + uint32_t aMaxNurseryBytes) +{ + MOZ_ASSERT(!mJSContext); + + mOwningThread->SetScriptObserver(this); + // The main thread has a base recursion depth of 0, workers of 1. + mBaseRecursionDepth = RecursionDepth(); + + mozilla::dom::InitScriptSettings(); + mJSContext = JS_NewContext(aMaxBytes, aMaxNurseryBytes, aParentContext); + if (!mJSContext) { + return NS_ERROR_OUT_OF_MEMORY; + } + + NS_GetCurrentThread()->SetCanInvokeJS(true); + + if (!JS_AddExtraGCRootsTracer(mJSContext, TraceBlackJS, this)) { + MOZ_CRASH("JS_AddExtraGCRootsTracer failed"); + } + JS_SetGrayGCRootsTracer(mJSContext, TraceGrayJS, this); + JS_SetGCCallback(mJSContext, GCCallback, this); + mPrevGCSliceCallback = JS::SetGCSliceCallback(mJSContext, GCSliceCallback); + + if (NS_IsMainThread()) { + // We would like to support all threads here, but the way timeline consumers + // are set up currently, you can either add a marker for one specific + // docshell, or for every consumer globally. We would like to add a marker + // for every consumer observing anything on this thread, but that is not + // currently possible. For now, add global markers only when we are on the + // main thread, since the UI for this tracing data only displays data + // relevant to the main-thread. + mPrevGCNurseryCollectionCallback = JS::SetGCNurseryCollectionCallback( + mJSContext, GCNurseryCollectionCallback); + } + + JS_SetObjectsTenuredCallback(mJSContext, JSObjectsTenuredCb, this); + JS::SetOutOfMemoryCallback(mJSContext, OutOfMemoryCallback, this); + JS::SetLargeAllocationFailureCallback(mJSContext, + LargeAllocationFailureCallback, this); + JS_SetDestroyZoneCallback(mJSContext, XPCStringConvert::FreeZoneCache); + JS_SetSweepZoneCallback(mJSContext, XPCStringConvert::ClearZoneCache); + JS::SetBuildIdOp(mJSContext, GetBuildId); + JS::SetWarningReporter(mJSContext, MozCrashWarningReporter); +#ifdef MOZ_CRASHREPORTER + js::AutoEnterOOMUnsafeRegion::setAnnotateOOMAllocationSizeCallback( + CrashReporter::AnnotateOOMAllocationSize); +#endif + + static js::DOMCallbacks DOMcallbacks = { + InstanceClassHasProtoAtDepth + }; + SetDOMCallbacks(mJSContext, &DOMcallbacks); + js::SetScriptEnvironmentPreparer(mJSContext, &mEnvironmentPreparer); + + JS::SetGetIncumbentGlobalCallback(mJSContext, GetIncumbentGlobalCallback); + +#ifdef SPIDERMONKEY_PROMISE + JS::SetEnqueuePromiseJobCallback(mJSContext, EnqueuePromiseJobCallback, this); + JS::SetPromiseRejectionTrackerCallback(mJSContext, PromiseRejectionTrackerCallback, this); + mUncaughtRejections.init(mJSContext, JS::GCVector<JSObject*, 0, js::SystemAllocPolicy>(js::SystemAllocPolicy())); + mConsumedRejections.init(mJSContext, JS::GCVector<JSObject*, 0, js::SystemAllocPolicy>(js::SystemAllocPolicy())); +#endif // SPIDERMONKEY_PROMISE + + JS::dbg::SetDebuggerMallocSizeOf(mJSContext, moz_malloc_size_of); + + nsCycleCollector_registerJSContext(this); + + return NS_OK; +} + +size_t +CycleCollectedJSContext::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const +{ + size_t n = 0; + + // We're deliberately not measuring anything hanging off the entries in + // mJSHolders. + n += mJSHolders.ShallowSizeOfExcludingThis(aMallocSizeOf); + + return n; +} + +void +CycleCollectedJSContext::UnmarkSkippableJSHolders() +{ + for (auto iter = mJSHolders.Iter(); !iter.Done(); iter.Next()) { + void* holder = iter.Key(); + nsScriptObjectTracer*& tracer = iter.Data(); + tracer->CanSkip(holder, true); + } +} + +void +CycleCollectedJSContext::DescribeGCThing(bool aIsMarked, JS::GCCellPtr aThing, + nsCycleCollectionTraversalCallback& aCb) const +{ + if (!aCb.WantDebugInfo()) { + aCb.DescribeGCedNode(aIsMarked, "JS Object"); + return; + } + + char name[72]; + uint64_t compartmentAddress = 0; + if (aThing.is<JSObject>()) { + JSObject* obj = &aThing.as<JSObject>(); + compartmentAddress = (uint64_t)js::GetObjectCompartment(obj); + const js::Class* clasp = js::GetObjectClass(obj); + + // Give the subclass a chance to do something + if (DescribeCustomObjects(obj, clasp, name)) { + // Nothing else to do! + } else if (js::IsFunctionObject(obj)) { + JSFunction* fun = JS_GetObjectFunction(obj); + JSString* str = JS_GetFunctionDisplayId(fun); + if (str) { + JSFlatString* flat = JS_ASSERT_STRING_IS_FLAT(str); + nsAutoString chars; + AssignJSFlatString(chars, flat); + NS_ConvertUTF16toUTF8 fname(chars); + SprintfLiteral(name, "JS Object (Function - %s)", fname.get()); + } else { + SprintfLiteral(name, "JS Object (Function)"); + } + } else { + SprintfLiteral(name, "JS Object (%s)", clasp->name); + } + } else { + SprintfLiteral(name, "JS %s", JS::GCTraceKindToAscii(aThing.kind())); + } + + // Disable printing global for objects while we figure out ObjShrink fallout. + aCb.DescribeGCedNode(aIsMarked, name, compartmentAddress); +} + +void +CycleCollectedJSContext::NoteGCThingJSChildren(JS::GCCellPtr aThing, + nsCycleCollectionTraversalCallback& aCb) const +{ + MOZ_ASSERT(mJSContext); + TraversalTracer trc(mJSContext, aCb); + JS::TraceChildren(&trc, aThing); +} + +void +CycleCollectedJSContext::NoteGCThingXPCOMChildren(const js::Class* aClasp, + JSObject* aObj, + nsCycleCollectionTraversalCallback& aCb) const +{ + MOZ_ASSERT(aClasp); + MOZ_ASSERT(aClasp == js::GetObjectClass(aObj)); + + if (NoteCustomGCThingXPCOMChildren(aClasp, aObj, aCb)) { + // Nothing else to do! + return; + } + // XXX This test does seem fragile, we should probably whitelist classes + // that do hold a strong reference, but that might not be possible. + else if (aClasp->flags & JSCLASS_HAS_PRIVATE && + aClasp->flags & JSCLASS_PRIVATE_IS_NSISUPPORTS) { + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "js::GetObjectPrivate(obj)"); + aCb.NoteXPCOMChild(static_cast<nsISupports*>(js::GetObjectPrivate(aObj))); + } else { + const DOMJSClass* domClass = GetDOMClass(aObj); + if (domClass) { + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "UnwrapDOMObject(obj)"); + // It's possible that our object is an unforgeable holder object, in + // which case it doesn't actually have a C++ DOM object associated with + // it. Use UnwrapPossiblyNotInitializedDOMObject, which produces null in + // that case, since NoteXPCOMChild/NoteNativeChild are null-safe. + if (domClass->mDOMObjectIsISupports) { + aCb.NoteXPCOMChild(UnwrapPossiblyNotInitializedDOMObject<nsISupports>(aObj)); + } else if (domClass->mParticipant) { + aCb.NoteNativeChild(UnwrapPossiblyNotInitializedDOMObject<void>(aObj), + domClass->mParticipant); + } + } + } +} + +void +CycleCollectedJSContext::TraverseGCThing(TraverseSelect aTs, JS::GCCellPtr aThing, + nsCycleCollectionTraversalCallback& aCb) +{ + bool isMarkedGray = JS::GCThingIsMarkedGray(aThing); + + if (aTs == TRAVERSE_FULL) { + DescribeGCThing(!isMarkedGray, aThing, aCb); + } + + // If this object is alive, then all of its children are alive. For JS objects, + // the black-gray invariant ensures the children are also marked black. For C++ + // objects, the ref count from this object will keep them alive. Thus we don't + // need to trace our children, unless we are debugging using WantAllTraces. + if (!isMarkedGray && !aCb.WantAllTraces()) { + return; + } + + if (aTs == TRAVERSE_FULL) { + NoteGCThingJSChildren(aThing, aCb); + } + + if (aThing.is<JSObject>()) { + JSObject* obj = &aThing.as<JSObject>(); + NoteGCThingXPCOMChildren(js::GetObjectClass(obj), obj, aCb); + } +} + +struct TraverseObjectShimClosure +{ + nsCycleCollectionTraversalCallback& cb; + CycleCollectedJSContext* self; +}; + +void +CycleCollectedJSContext::TraverseZone(JS::Zone* aZone, + nsCycleCollectionTraversalCallback& aCb) +{ + MOZ_ASSERT(mJSContext); + + /* + * We treat the zone as being gray. We handle non-gray GCthings in the + * zone by not reporting their children to the CC. The black-gray invariant + * ensures that any JS children will also be non-gray, and thus don't need to be + * added to the graph. For C++ children, not representing the edge from the + * non-gray JS GCthings to the C++ object will keep the child alive. + * + * We don't allow zone merging in a WantAllTraces CC, because then these + * assumptions don't hold. + */ + aCb.DescribeGCedNode(false, "JS Zone"); + + /* + * Every JS child of everything in the zone is either in the zone + * or is a cross-compartment wrapper. In the former case, we don't need to + * represent these edges in the CC graph because JS objects are not ref counted. + * In the latter case, the JS engine keeps a map of these wrappers, which we + * iterate over. Edges between compartments in the same zone will add + * unnecessary loop edges to the graph (bug 842137). + */ + TraversalTracer trc(mJSContext, aCb); + js::VisitGrayWrapperTargets(aZone, NoteJSChildGrayWrapperShim, &trc); + + /* + * To find C++ children of things in the zone, we scan every JS Object in + * the zone. Only JS Objects can have C++ children. + */ + TraverseObjectShimClosure closure = { aCb, this }; + js::IterateGrayObjects(aZone, TraverseObjectShim, &closure); +} + +/* static */ void +CycleCollectedJSContext::TraverseObjectShim(void* aData, JS::GCCellPtr aThing) +{ + TraverseObjectShimClosure* closure = + static_cast<TraverseObjectShimClosure*>(aData); + + MOZ_ASSERT(aThing.is<JSObject>()); + closure->self->TraverseGCThing(CycleCollectedJSContext::TRAVERSE_CPP, + aThing, closure->cb); +} + +void +CycleCollectedJSContext::TraverseNativeRoots(nsCycleCollectionNoteRootCallback& aCb) +{ + // NB: This is here just to preserve the existing XPConnect order. I doubt it + // would hurt to do this after the JS holders. + TraverseAdditionalNativeRoots(aCb); + + for (auto iter = mJSHolders.Iter(); !iter.Done(); iter.Next()) { + void* holder = iter.Key(); + nsScriptObjectTracer*& tracer = iter.Data(); + + bool noteRoot = false; + if (MOZ_UNLIKELY(aCb.WantAllTraces())) { + noteRoot = true; + } else { + tracer->Trace(holder, + TraceCallbackFunc(CheckParticipatesInCycleCollection), + ¬eRoot); + } + + if (noteRoot) { + aCb.NoteNativeRoot(holder, tracer); + } + } +} + +/* static */ void +CycleCollectedJSContext::TraceBlackJS(JSTracer* aTracer, void* aData) +{ + CycleCollectedJSContext* self = static_cast<CycleCollectedJSContext*>(aData); + + self->TraceNativeBlackRoots(aTracer); +} + +/* static */ void +CycleCollectedJSContext::TraceGrayJS(JSTracer* aTracer, void* aData) +{ + CycleCollectedJSContext* self = static_cast<CycleCollectedJSContext*>(aData); + + // Mark these roots as gray so the CC can walk them later. + self->TraceNativeGrayRoots(aTracer); +} + +/* static */ void +CycleCollectedJSContext::GCCallback(JSContext* aContext, + JSGCStatus aStatus, + void* aData) +{ + CycleCollectedJSContext* self = static_cast<CycleCollectedJSContext*>(aData); + + MOZ_ASSERT(aContext == self->Context()); + + self->OnGC(aStatus); +} + +/* static */ void +CycleCollectedJSContext::GCSliceCallback(JSContext* aContext, + JS::GCProgress aProgress, + const JS::GCDescription& aDesc) +{ + CycleCollectedJSContext* self = CycleCollectedJSContext::Get(); + MOZ_ASSERT(self->Context() == aContext); + + if (aProgress == JS::GC_CYCLE_END) { + JS::gcreason::Reason reason = aDesc.reason_; + Unused << + NS_WARN_IF(NS_FAILED(DebuggerOnGCRunnable::Enqueue(aContext, aDesc)) && + reason != JS::gcreason::SHUTDOWN_CC && + reason != JS::gcreason::DESTROY_RUNTIME && + reason != JS::gcreason::XPCONNECT_SHUTDOWN); + } + + if (self->mPrevGCSliceCallback) { + self->mPrevGCSliceCallback(aContext, aProgress, aDesc); + } +} + +class MinorGCMarker : public TimelineMarker +{ +private: + JS::gcreason::Reason mReason; + +public: + MinorGCMarker(MarkerTracingType aTracingType, + JS::gcreason::Reason aReason) + : TimelineMarker("MinorGC", + aTracingType, + MarkerStackRequest::NO_STACK) + , mReason(aReason) + { + MOZ_ASSERT(aTracingType == MarkerTracingType::START || + aTracingType == MarkerTracingType::END); + } + + MinorGCMarker(JS::GCNurseryProgress aProgress, + JS::gcreason::Reason aReason) + : TimelineMarker("MinorGC", + aProgress == JS::GCNurseryProgress::GC_NURSERY_COLLECTION_START + ? MarkerTracingType::START + : MarkerTracingType::END, + MarkerStackRequest::NO_STACK) + , mReason(aReason) + { } + + virtual void + AddDetails(JSContext* aCx, + dom::ProfileTimelineMarker& aMarker) override + { + TimelineMarker::AddDetails(aCx, aMarker); + + if (GetTracingType() == MarkerTracingType::START) { + auto reason = JS::gcreason::ExplainReason(mReason); + aMarker.mCauseName.Construct(NS_ConvertUTF8toUTF16(reason)); + } + } + + virtual UniquePtr<AbstractTimelineMarker> + Clone() override + { + auto clone = MakeUnique<MinorGCMarker>(GetTracingType(), mReason); + clone->SetCustomTime(GetTime()); + return UniquePtr<AbstractTimelineMarker>(Move(clone)); + } +}; + +/* static */ void +CycleCollectedJSContext::GCNurseryCollectionCallback(JSContext* aContext, + JS::GCNurseryProgress aProgress, + JS::gcreason::Reason aReason) +{ + CycleCollectedJSContext* self = CycleCollectedJSContext::Get(); + MOZ_ASSERT(self->Context() == aContext); + MOZ_ASSERT(NS_IsMainThread()); + + RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get(); + if (timelines && !timelines->IsEmpty()) { + UniquePtr<AbstractTimelineMarker> abstractMarker( + MakeUnique<MinorGCMarker>(aProgress, aReason)); + timelines->AddMarkerForAllObservedDocShells(abstractMarker); + } + + if (self->mPrevGCNurseryCollectionCallback) { + self->mPrevGCNurseryCollectionCallback(aContext, aProgress, aReason); + } +} + + +/* static */ void +CycleCollectedJSContext::OutOfMemoryCallback(JSContext* aContext, + void* aData) +{ + CycleCollectedJSContext* self = static_cast<CycleCollectedJSContext*>(aData); + + MOZ_ASSERT(aContext == self->Context()); + + self->OnOutOfMemory(); +} + +/* static */ void +CycleCollectedJSContext::LargeAllocationFailureCallback(void* aData) +{ + CycleCollectedJSContext* self = static_cast<CycleCollectedJSContext*>(aData); + + self->OnLargeAllocationFailure(); +} + +class PromiseJobRunnable final : public Runnable +{ +public: + PromiseJobRunnable(JS::HandleObject aCallback, JS::HandleObject aAllocationSite, + nsIGlobalObject* aIncumbentGlobal) + : mCallback(new PromiseJobCallback(aCallback, aAllocationSite, aIncumbentGlobal)) + { + } + + virtual ~PromiseJobRunnable() + { + } + +protected: + NS_IMETHOD + Run() override + { + nsIGlobalObject* global = xpc::NativeGlobal(mCallback->CallbackPreserveColor()); + if (global && !global->IsDying()) { + mCallback->Call("promise callback"); + } + return NS_OK; + } + +private: + RefPtr<PromiseJobCallback> mCallback; +}; + +/* static */ +JSObject* +CycleCollectedJSContext::GetIncumbentGlobalCallback(JSContext* aCx) +{ + nsIGlobalObject* global = mozilla::dom::GetIncumbentGlobal(); + if (global) { + return global->GetGlobalJSObject(); + } + return nullptr; +} + +/* static */ +bool +CycleCollectedJSContext::EnqueuePromiseJobCallback(JSContext* aCx, + JS::HandleObject aJob, + JS::HandleObject aAllocationSite, + JS::HandleObject aIncumbentGlobal, + void* aData) +{ + CycleCollectedJSContext* self = static_cast<CycleCollectedJSContext*>(aData); + MOZ_ASSERT(aCx == self->Context()); + MOZ_ASSERT(Get() == self); + + nsIGlobalObject* global = nullptr; + if (aIncumbentGlobal) { + global = xpc::NativeGlobal(aIncumbentGlobal); + } + nsCOMPtr<nsIRunnable> runnable = new PromiseJobRunnable(aJob, aAllocationSite, global); + self->DispatchToMicroTask(runnable.forget()); + return true; +} + +#ifdef SPIDERMONKEY_PROMISE +/* static */ +void +CycleCollectedJSContext::PromiseRejectionTrackerCallback(JSContext* aCx, + JS::HandleObject aPromise, + PromiseRejectionHandlingState state, + void* aData) +{ +#ifdef DEBUG + CycleCollectedJSContext* self = static_cast<CycleCollectedJSContext*>(aData); +#endif // DEBUG + MOZ_ASSERT(aCx == self->Context()); + MOZ_ASSERT(Get() == self); + + if (state == PromiseRejectionHandlingState::Unhandled) { + PromiseDebugging::AddUncaughtRejection(aPromise); + } else { + PromiseDebugging::AddConsumedRejection(aPromise); + } +} +#endif // SPIDERMONKEY_PROMISE + +struct JsGcTracer : public TraceCallbacks +{ + virtual void Trace(JS::Heap<JS::Value>* aPtr, const char* aName, + void* aClosure) const override + { + JS::TraceEdge(static_cast<JSTracer*>(aClosure), aPtr, aName); + } + virtual void Trace(JS::Heap<jsid>* aPtr, const char* aName, + void* aClosure) const override + { + JS::TraceEdge(static_cast<JSTracer*>(aClosure), aPtr, aName); + } + virtual void Trace(JS::Heap<JSObject*>* aPtr, const char* aName, + void* aClosure) const override + { + JS::TraceEdge(static_cast<JSTracer*>(aClosure), aPtr, aName); + } + virtual void Trace(JSObject** aPtr, const char* aName, + void* aClosure) const override + { + js::UnsafeTraceManuallyBarrieredEdge(static_cast<JSTracer*>(aClosure), aPtr, aName); + } + virtual void Trace(JS::TenuredHeap<JSObject*>* aPtr, const char* aName, + void* aClosure) const override + { + JS::TraceEdge(static_cast<JSTracer*>(aClosure), aPtr, aName); + } + virtual void Trace(JS::Heap<JSString*>* aPtr, const char* aName, + void* aClosure) const override + { + JS::TraceEdge(static_cast<JSTracer*>(aClosure), aPtr, aName); + } + virtual void Trace(JS::Heap<JSScript*>* aPtr, const char* aName, + void* aClosure) const override + { + JS::TraceEdge(static_cast<JSTracer*>(aClosure), aPtr, aName); + } + virtual void Trace(JS::Heap<JSFunction*>* aPtr, const char* aName, + void* aClosure) const override + { + JS::TraceEdge(static_cast<JSTracer*>(aClosure), aPtr, aName); + } +}; + +void +mozilla::TraceScriptHolder(nsISupports* aHolder, JSTracer* aTracer) +{ + nsXPCOMCycleCollectionParticipant* participant = nullptr; + CallQueryInterface(aHolder, &participant); + participant->Trace(aHolder, JsGcTracer(), aTracer); +} + +void +CycleCollectedJSContext::TraceNativeGrayRoots(JSTracer* aTracer) +{ + MOZ_ASSERT(mJSContext); + + // NB: This is here just to preserve the existing XPConnect order. I doubt it + // would hurt to do this after the JS holders. + TraceAdditionalNativeGrayRoots(aTracer); + + for (auto iter = mJSHolders.Iter(); !iter.Done(); iter.Next()) { + void* holder = iter.Key(); + nsScriptObjectTracer*& tracer = iter.Data(); + tracer->Trace(holder, JsGcTracer(), aTracer); + } +} + +void +CycleCollectedJSContext::AddJSHolder(void* aHolder, nsScriptObjectTracer* aTracer) +{ + MOZ_ASSERT(mJSContext); + mJSHolders.Put(aHolder, aTracer); +} + +struct ClearJSHolder : public TraceCallbacks +{ + virtual void Trace(JS::Heap<JS::Value>* aPtr, const char*, void*) const override + { + aPtr->setUndefined(); + } + + virtual void Trace(JS::Heap<jsid>* aPtr, const char*, void*) const override + { + *aPtr = JSID_VOID; + } + + virtual void Trace(JS::Heap<JSObject*>* aPtr, const char*, void*) const override + { + *aPtr = nullptr; + } + + virtual void Trace(JSObject** aPtr, const char* aName, + void* aClosure) const override + { + *aPtr = nullptr; + } + + virtual void Trace(JS::TenuredHeap<JSObject*>* aPtr, const char*, void*) const override + { + *aPtr = nullptr; + } + + virtual void Trace(JS::Heap<JSString*>* aPtr, const char*, void*) const override + { + *aPtr = nullptr; + } + + virtual void Trace(JS::Heap<JSScript*>* aPtr, const char*, void*) const override + { + *aPtr = nullptr; + } + + virtual void Trace(JS::Heap<JSFunction*>* aPtr, const char*, void*) const override + { + *aPtr = nullptr; + } +}; + +void +CycleCollectedJSContext::RemoveJSHolder(void* aHolder) +{ + MOZ_ASSERT(mJSContext); + + nsScriptObjectTracer* tracer = mJSHolders.Get(aHolder); + if (!tracer) { + return; + } + tracer->Trace(aHolder, ClearJSHolder(), nullptr); + mJSHolders.Remove(aHolder); +} + +#ifdef DEBUG +bool +CycleCollectedJSContext::IsJSHolder(void* aHolder) +{ + MOZ_ASSERT(mJSContext); + return mJSHolders.Get(aHolder, nullptr); +} + +static void +AssertNoGcThing(JS::GCCellPtr aGCThing, const char* aName, void* aClosure) +{ + MOZ_ASSERT(!aGCThing); +} + +void +CycleCollectedJSContext::AssertNoObjectsToTrace(void* aPossibleJSHolder) +{ + MOZ_ASSERT(mJSContext); + + nsScriptObjectTracer* tracer = mJSHolders.Get(aPossibleJSHolder); + if (tracer) { + tracer->Trace(aPossibleJSHolder, TraceCallbackFunc(AssertNoGcThing), nullptr); + } +} +#endif + +already_AddRefed<nsIException> +CycleCollectedJSContext::GetPendingException() const +{ + MOZ_ASSERT(mJSContext); + + nsCOMPtr<nsIException> out = mPendingException; + return out.forget(); +} + +void +CycleCollectedJSContext::SetPendingException(nsIException* aException) +{ + MOZ_ASSERT(mJSContext); + mPendingException = aException; +} + +std::queue<nsCOMPtr<nsIRunnable>>& +CycleCollectedJSContext::GetPromiseMicroTaskQueue() +{ + MOZ_ASSERT(mJSContext); + return mPromiseMicroTaskQueue; +} + +std::queue<nsCOMPtr<nsIRunnable>>& +CycleCollectedJSContext::GetDebuggerPromiseMicroTaskQueue() +{ + MOZ_ASSERT(mJSContext); + return mDebuggerPromiseMicroTaskQueue; +} + +nsCycleCollectionParticipant* +CycleCollectedJSContext::GCThingParticipant() +{ + MOZ_ASSERT(mJSContext); + return &mGCThingCycleCollectorGlobal; +} + +nsCycleCollectionParticipant* +CycleCollectedJSContext::ZoneParticipant() +{ + MOZ_ASSERT(mJSContext); + return &mJSZoneCycleCollectorGlobal; +} + +nsresult +CycleCollectedJSContext::TraverseRoots(nsCycleCollectionNoteRootCallback& aCb) +{ + MOZ_ASSERT(mJSContext); + + TraverseNativeRoots(aCb); + + NoteWeakMapsTracer trc(mJSContext, aCb); + js::TraceWeakMaps(&trc); + + return NS_OK; +} + +bool +CycleCollectedJSContext::UsefulToMergeZones() const +{ + return false; +} + +void +CycleCollectedJSContext::FixWeakMappingGrayBits() const +{ + MOZ_ASSERT(mJSContext); + MOZ_ASSERT(!JS::IsIncrementalGCInProgress(mJSContext), + "Don't call FixWeakMappingGrayBits during a GC."); + FixWeakMappingGrayBitsTracer fixer(mJSContext); + fixer.FixAll(); +} + +bool +CycleCollectedJSContext::AreGCGrayBitsValid() const +{ + MOZ_ASSERT(mJSContext); + return js::AreGCGrayBitsValid(mJSContext); +} + +void +CycleCollectedJSContext::GarbageCollect(uint32_t aReason) const +{ + MOZ_ASSERT(mJSContext); + + MOZ_ASSERT(aReason < JS::gcreason::NUM_REASONS); + JS::gcreason::Reason gcreason = static_cast<JS::gcreason::Reason>(aReason); + + JS::PrepareForFullGC(mJSContext); + JS::GCForReason(mJSContext, GC_NORMAL, gcreason); +} + +void +CycleCollectedJSContext::JSObjectsTenured() +{ + MOZ_ASSERT(mJSContext); + + for (auto iter = mNurseryObjects.Iter(); !iter.Done(); iter.Next()) { + nsWrapperCache* cache = iter.Get(); + JSObject* wrapper = cache->GetWrapperPreserveColor(); + MOZ_ASSERT(wrapper); + if (!JS::ObjectIsTenured(wrapper)) { + MOZ_ASSERT(!cache->PreservingWrapper()); + const JSClass* jsClass = js::GetObjectJSClass(wrapper); + jsClass->doFinalize(nullptr, wrapper); + } + } + +#ifdef DEBUG +for (auto iter = mPreservedNurseryObjects.Iter(); !iter.Done(); iter.Next()) { + MOZ_ASSERT(JS::ObjectIsTenured(iter.Get().get())); +} +#endif + + mNurseryObjects.Clear(); + mPreservedNurseryObjects.Clear(); +} + +void +CycleCollectedJSContext::NurseryWrapperAdded(nsWrapperCache* aCache) +{ + MOZ_ASSERT(mJSContext); + MOZ_ASSERT(aCache); + MOZ_ASSERT(aCache->GetWrapperPreserveColor()); + MOZ_ASSERT(!JS::ObjectIsTenured(aCache->GetWrapperPreserveColor())); + mNurseryObjects.InfallibleAppend(aCache); +} + +void +CycleCollectedJSContext::NurseryWrapperPreserved(JSObject* aWrapper) +{ + MOZ_ASSERT(mJSContext); + + mPreservedNurseryObjects.InfallibleAppend( + JS::PersistentRooted<JSObject*>(mJSContext, aWrapper)); +} + +void +CycleCollectedJSContext::DeferredFinalize(DeferredFinalizeAppendFunction aAppendFunc, + DeferredFinalizeFunction aFunc, + void* aThing) +{ + MOZ_ASSERT(mJSContext); + + void* thingArray = nullptr; + bool hadThingArray = mDeferredFinalizerTable.Get(aFunc, &thingArray); + + thingArray = aAppendFunc(thingArray, aThing); + if (!hadThingArray) { + mDeferredFinalizerTable.Put(aFunc, thingArray); + } +} + +void +CycleCollectedJSContext::DeferredFinalize(nsISupports* aSupports) +{ + MOZ_ASSERT(mJSContext); + + typedef DeferredFinalizerImpl<nsISupports> Impl; + DeferredFinalize(Impl::AppendDeferredFinalizePointer, Impl::DeferredFinalize, + aSupports); +} + +void +CycleCollectedJSContext::DumpJSHeap(FILE* aFile) +{ + js::DumpHeap(Context(), aFile, js::CollectNurseryBeforeDump); +} + +void +CycleCollectedJSContext::ProcessStableStateQueue() +{ + MOZ_ASSERT(mJSContext); + MOZ_RELEASE_ASSERT(!mDoingStableStates); + mDoingStableStates = true; + + for (uint32_t i = 0; i < mStableStateEvents.Length(); ++i) { + nsCOMPtr<nsIRunnable> event = mStableStateEvents[i].forget(); + event->Run(); + } + + mStableStateEvents.Clear(); + mDoingStableStates = false; +} + +void +CycleCollectedJSContext::ProcessMetastableStateQueue(uint32_t aRecursionDepth) +{ + MOZ_ASSERT(mJSContext); + MOZ_RELEASE_ASSERT(!mDoingStableStates); + mDoingStableStates = true; + + nsTArray<RunInMetastableStateData> localQueue = Move(mMetastableStateEvents); + + for (uint32_t i = 0; i < localQueue.Length(); ++i) + { + RunInMetastableStateData& data = localQueue[i]; + if (data.mRecursionDepth != aRecursionDepth) { + continue; + } + + { + nsCOMPtr<nsIRunnable> runnable = data.mRunnable.forget(); + runnable->Run(); + } + + localQueue.RemoveElementAt(i--); + } + + // If the queue has events in it now, they were added from something we called, + // so they belong at the end of the queue. + localQueue.AppendElements(mMetastableStateEvents); + localQueue.SwapElements(mMetastableStateEvents); + mDoingStableStates = false; +} + +void +CycleCollectedJSContext::AfterProcessTask(uint32_t aRecursionDepth) +{ + MOZ_ASSERT(mJSContext); + + // See HTML 6.1.4.2 Processing model + + // Execute any events that were waiting for a microtask to complete. + // This is not (yet) in the spec. + ProcessMetastableStateQueue(aRecursionDepth); + + // Step 4.1: Execute microtasks. + if (!mDisableMicroTaskCheckpoint) { + if (NS_IsMainThread()) { + nsContentUtils::PerformMainThreadMicroTaskCheckpoint(); + Promise::PerformMicroTaskCheckpoint(); + } else { + Promise::PerformWorkerMicroTaskCheckpoint(); + } + } + + // Step 4.2 Execute any events that were waiting for a stable state. + ProcessStableStateQueue(); +} + +void +CycleCollectedJSContext::AfterProcessMicrotask() +{ + MOZ_ASSERT(mJSContext); + AfterProcessMicrotask(RecursionDepth()); +} + +void +CycleCollectedJSContext::AfterProcessMicrotask(uint32_t aRecursionDepth) +{ + MOZ_ASSERT(mJSContext); + + // Between microtasks, execute any events that were waiting for a microtask + // to complete. + ProcessMetastableStateQueue(aRecursionDepth); +} + +uint32_t +CycleCollectedJSContext::RecursionDepth() +{ + return mOwningThread->RecursionDepth(); +} + +void +CycleCollectedJSContext::RunInStableState(already_AddRefed<nsIRunnable>&& aRunnable) +{ + MOZ_ASSERT(mJSContext); + mStableStateEvents.AppendElement(Move(aRunnable)); +} + +void +CycleCollectedJSContext::RunInMetastableState(already_AddRefed<nsIRunnable>&& aRunnable) +{ + MOZ_ASSERT(mJSContext); + + RunInMetastableStateData data; + data.mRunnable = aRunnable; + + MOZ_ASSERT(mOwningThread); + data.mRecursionDepth = RecursionDepth(); + + // There must be an event running to get here. +#ifndef MOZ_WIDGET_COCOA + MOZ_ASSERT(data.mRecursionDepth > mBaseRecursionDepth); +#else + // XXX bug 1261143 + // Recursion depth should be greater than mBaseRecursionDepth, + // or the runnable will stay in the queue forever. + if (data.mRecursionDepth <= mBaseRecursionDepth) { + data.mRecursionDepth = mBaseRecursionDepth + 1; + } +#endif + + mMetastableStateEvents.AppendElement(Move(data)); +} + +IncrementalFinalizeRunnable::IncrementalFinalizeRunnable(CycleCollectedJSContext* aCx, + DeferredFinalizerTable& aFinalizers) + : mContext(aCx) + , mFinalizeFunctionToRun(0) + , mReleasing(false) +{ + for (auto iter = aFinalizers.Iter(); !iter.Done(); iter.Next()) { + DeferredFinalizeFunction& function = iter.Key(); + void*& data = iter.Data(); + + DeferredFinalizeFunctionHolder* holder = + mDeferredFinalizeFunctions.AppendElement(); + holder->run = function; + holder->data = data; + + iter.Remove(); + } +} + +IncrementalFinalizeRunnable::~IncrementalFinalizeRunnable() +{ + MOZ_ASSERT(this != mContext->mFinalizeRunnable); +} + +void +IncrementalFinalizeRunnable::ReleaseNow(bool aLimited) +{ + if (mReleasing) { + NS_WARNING("Re-entering ReleaseNow"); + return; + } + { + mozilla::AutoRestore<bool> ar(mReleasing); + mReleasing = true; + MOZ_ASSERT(mDeferredFinalizeFunctions.Length() != 0, + "We should have at least ReleaseSliceNow to run"); + MOZ_ASSERT(mFinalizeFunctionToRun < mDeferredFinalizeFunctions.Length(), + "No more finalizers to run?"); + + TimeDuration sliceTime = TimeDuration::FromMilliseconds(SliceMillis); + TimeStamp started = TimeStamp::Now(); + bool timeout = false; + do { + const DeferredFinalizeFunctionHolder& function = + mDeferredFinalizeFunctions[mFinalizeFunctionToRun]; + if (aLimited) { + bool done = false; + while (!timeout && !done) { + /* + * We don't want to read the clock too often, so we try to + * release slices of 100 items. + */ + done = function.run(100, function.data); + timeout = TimeStamp::Now() - started >= sliceTime; + } + if (done) { + ++mFinalizeFunctionToRun; + } + if (timeout) { + break; + } + } else { + while (!function.run(UINT32_MAX, function.data)); + ++mFinalizeFunctionToRun; + } + } while (mFinalizeFunctionToRun < mDeferredFinalizeFunctions.Length()); + } + + if (mFinalizeFunctionToRun == mDeferredFinalizeFunctions.Length()) { + MOZ_ASSERT(mContext->mFinalizeRunnable == this); + mDeferredFinalizeFunctions.Clear(); + // NB: This may delete this! + mContext->mFinalizeRunnable = nullptr; + } +} + +NS_IMETHODIMP +IncrementalFinalizeRunnable::Run() +{ + if (mContext->mFinalizeRunnable != this) { + /* These items were already processed synchronously in JSGC_END. */ + MOZ_ASSERT(!mDeferredFinalizeFunctions.Length()); + return NS_OK; + } + + TimeStamp start = TimeStamp::Now(); + ReleaseNow(true); + + if (mDeferredFinalizeFunctions.Length()) { + nsresult rv = NS_DispatchToCurrentThread(this); + if (NS_FAILED(rv)) { + ReleaseNow(false); + } + } + + uint32_t duration = (uint32_t)((TimeStamp::Now() - start).ToMilliseconds()); + Telemetry::Accumulate(Telemetry::DEFERRED_FINALIZE_ASYNC, duration); + + return NS_OK; +} + +void +CycleCollectedJSContext::FinalizeDeferredThings(DeferredFinalizeType aType) +{ + MOZ_ASSERT(mJSContext); + + /* + * If the previous GC created a runnable to finalize objects + * incrementally, and if it hasn't finished yet, finish it now. We + * don't want these to build up. We also don't want to allow any + * existing incremental finalize runnables to run after a + * non-incremental GC, since they are often used to detect leaks. + */ + if (mFinalizeRunnable) { + mFinalizeRunnable->ReleaseNow(false); + if (mFinalizeRunnable) { + // If we re-entered ReleaseNow, we couldn't delete mFinalizeRunnable and + // we need to just continue processing it. + return; + } + } + + if (mDeferredFinalizerTable.Count() == 0) { + return; + } + + mFinalizeRunnable = new IncrementalFinalizeRunnable(this, + mDeferredFinalizerTable); + + // Everything should be gone now. + MOZ_ASSERT(mDeferredFinalizerTable.Count() == 0); + + if (aType == FinalizeIncrementally) { + NS_DispatchToCurrentThread(mFinalizeRunnable); + } else { + mFinalizeRunnable->ReleaseNow(false); + MOZ_ASSERT(!mFinalizeRunnable); + } +} + +void +CycleCollectedJSContext::AnnotateAndSetOutOfMemory(OOMState* aStatePtr, + OOMState aNewState) +{ + MOZ_ASSERT(mJSContext); + + *aStatePtr = aNewState; +#ifdef MOZ_CRASHREPORTER + CrashReporter::AnnotateCrashReport(aStatePtr == &mOutOfMemoryState + ? NS_LITERAL_CSTRING("JSOutOfMemory") + : NS_LITERAL_CSTRING("JSLargeAllocationFailure"), + aNewState == OOMState::Reporting + ? NS_LITERAL_CSTRING("Reporting") + : aNewState == OOMState::Reported + ? NS_LITERAL_CSTRING("Reported") + : NS_LITERAL_CSTRING("Recovered")); +#endif +} + +void +CycleCollectedJSContext::OnGC(JSGCStatus aStatus) +{ + MOZ_ASSERT(mJSContext); + + switch (aStatus) { + case JSGC_BEGIN: + nsCycleCollector_prepareForGarbageCollection(); + mZonesWaitingForGC.Clear(); + break; + case JSGC_END: { +#ifdef MOZ_CRASHREPORTER + if (mOutOfMemoryState == OOMState::Reported) { + AnnotateAndSetOutOfMemory(&mOutOfMemoryState, OOMState::Recovered); + } + if (mLargeAllocationFailureState == OOMState::Reported) { + AnnotateAndSetOutOfMemory(&mLargeAllocationFailureState, OOMState::Recovered); + } +#endif + + // Do any deferred finalization of native objects. + FinalizeDeferredThings(JS::WasIncrementalGC(mJSContext) ? FinalizeIncrementally : + FinalizeNow); + break; + } + default: + MOZ_CRASH(); + } + + CustomGCCallback(aStatus); +} + +void +CycleCollectedJSContext::OnOutOfMemory() +{ + MOZ_ASSERT(mJSContext); + + AnnotateAndSetOutOfMemory(&mOutOfMemoryState, OOMState::Reporting); + CustomOutOfMemoryCallback(); + AnnotateAndSetOutOfMemory(&mOutOfMemoryState, OOMState::Reported); +} + +void +CycleCollectedJSContext::OnLargeAllocationFailure() +{ + MOZ_ASSERT(mJSContext); + + AnnotateAndSetOutOfMemory(&mLargeAllocationFailureState, OOMState::Reporting); + CustomLargeAllocationFailureCallback(); + AnnotateAndSetOutOfMemory(&mLargeAllocationFailureState, OOMState::Reported); +} + +void +CycleCollectedJSContext::PrepareWaitingZonesForGC() +{ + if (mZonesWaitingForGC.Count() == 0) { + JS::PrepareForFullGC(Context()); + } else { + for (auto iter = mZonesWaitingForGC.Iter(); !iter.Done(); iter.Next()) { + JS::PrepareZoneForGC(iter.Get()->GetKey()); + } + mZonesWaitingForGC.Clear(); + } +} + +void +CycleCollectedJSContext::DispatchToMicroTask(already_AddRefed<nsIRunnable> aRunnable) +{ + RefPtr<nsIRunnable> runnable(aRunnable); + + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(runnable); + + mPromiseMicroTaskQueue.push(runnable.forget()); +} + +void +CycleCollectedJSContext::EnvironmentPreparer::invoke(JS::HandleObject scope, + js::ScriptEnvironmentPreparer::Closure& closure) +{ + nsIGlobalObject* global = xpc::NativeGlobal(scope); + + // Not much we can do if we simply don't have a usable global here... + NS_ENSURE_TRUE_VOID(global && global->GetGlobalJSObject()); + + AutoEntryScript aes(global, "JS-engine-initiated execution"); + + MOZ_ASSERT(!JS_IsExceptionPending(aes.cx())); + + DebugOnly<bool> ok = closure(aes.cx()); + + MOZ_ASSERT_IF(ok, !JS_IsExceptionPending(aes.cx())); + + // The AutoEntryScript will check for JS_IsExceptionPending on the + // JSContext and report it as needed as it comes off the stack. +} diff --git a/xpcom/base/CycleCollectedJSContext.h b/xpcom/base/CycleCollectedJSContext.h new file mode 100644 index 000000000..9415634b8 --- /dev/null +++ b/xpcom/base/CycleCollectedJSContext.h @@ -0,0 +1,494 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_CycleCollectedJSContext_h__ +#define mozilla_CycleCollectedJSContext_h__ + +#include <queue> + +#include "mozilla/DeferredFinalize.h" +#include "mozilla/mozalloc.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/SegmentedVector.h" +#include "jsapi.h" +#include "jsfriendapi.h" + +#include "nsCycleCollectionParticipant.h" +#include "nsDataHashtable.h" +#include "nsHashKeys.h" +#include "nsTArray.h" +#include "nsTHashtable.h" + +class nsCycleCollectionNoteRootCallback; +class nsIException; +class nsIRunnable; +class nsThread; +class nsWrapperCache; + +namespace js { +struct Class; +} // namespace js + +namespace mozilla { + +class JSGCThingParticipant: public nsCycleCollectionParticipant +{ +public: + NS_IMETHOD_(void) Root(void*) override + { + MOZ_ASSERT(false, "Don't call Root on GC things"); + } + + NS_IMETHOD_(void) Unlink(void*) override + { + MOZ_ASSERT(false, "Don't call Unlink on GC things, as they may be dead"); + } + + NS_IMETHOD_(void) Unroot(void*) override + { + MOZ_ASSERT(false, "Don't call Unroot on GC things, as they may be dead"); + } + + NS_IMETHOD_(void) DeleteCycleCollectable(void* aPtr) override + { + MOZ_ASSERT(false, "Can't directly delete a cycle collectable GC thing"); + } + + NS_IMETHOD Traverse(void* aPtr, nsCycleCollectionTraversalCallback& aCb) + override; + + NS_DECL_CYCLE_COLLECTION_CLASS_NAME_METHOD(JSGCThingParticipant) +}; + +class JSZoneParticipant : public nsCycleCollectionParticipant +{ +public: + constexpr JSZoneParticipant(): nsCycleCollectionParticipant() + { + } + + NS_IMETHOD_(void) Root(void*) override + { + MOZ_ASSERT(false, "Don't call Root on GC things"); + } + + NS_IMETHOD_(void) Unlink(void*) override + { + MOZ_ASSERT(false, "Don't call Unlink on GC things, as they may be dead"); + } + + NS_IMETHOD_(void) Unroot(void*) override + { + MOZ_ASSERT(false, "Don't call Unroot on GC things, as they may be dead"); + } + + NS_IMETHOD_(void) DeleteCycleCollectable(void*) override + { + MOZ_ASSERT(false, "Can't directly delete a cycle collectable GC thing"); + } + + NS_IMETHOD Traverse(void* aPtr, nsCycleCollectionTraversalCallback& aCb) + override; + + NS_DECL_CYCLE_COLLECTION_CLASS_NAME_METHOD(JSZoneParticipant) +}; + +class IncrementalFinalizeRunnable; + +// Contains various stats about the cycle collection. +struct CycleCollectorResults +{ + CycleCollectorResults() + { + // Initialize here so when we increment mNumSlices the first time we're + // not using uninitialized memory. + Init(); + } + + void Init() + { + mForcedGC = false; + mMergedZones = false; + mAnyManual = false; + mVisitedRefCounted = 0; + mVisitedGCed = 0; + mFreedRefCounted = 0; + mFreedGCed = 0; + mFreedJSZones = 0; + mNumSlices = 1; + // mNumSlices is initialized to one, because we call Init() after the + // per-slice increment of mNumSlices has already occurred. + } + + bool mForcedGC; + bool mMergedZones; + bool mAnyManual; // true if any slice of the CC was manually triggered, or at shutdown. + uint32_t mVisitedRefCounted; + uint32_t mVisitedGCed; + uint32_t mFreedRefCounted; + uint32_t mFreedGCed; + uint32_t mFreedJSZones; + uint32_t mNumSlices; +}; + +class CycleCollectedJSContext +{ + friend class JSGCThingParticipant; + friend class JSZoneParticipant; + friend class IncrementalFinalizeRunnable; +protected: + CycleCollectedJSContext(); + virtual ~CycleCollectedJSContext(); + + MOZ_IS_CLASS_INIT + nsresult Initialize(JSContext* aParentContext, + uint32_t aMaxBytes, + uint32_t aMaxNurseryBytes); + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + void UnmarkSkippableJSHolders(); + + virtual void + TraverseAdditionalNativeRoots(nsCycleCollectionNoteRootCallback& aCb) {} + virtual void TraceAdditionalNativeGrayRoots(JSTracer* aTracer) {} + + virtual void CustomGCCallback(JSGCStatus aStatus) {} + virtual void CustomOutOfMemoryCallback() {} + virtual void CustomLargeAllocationFailureCallback() {} + + std::queue<nsCOMPtr<nsIRunnable>> mPromiseMicroTaskQueue; + std::queue<nsCOMPtr<nsIRunnable>> mDebuggerPromiseMicroTaskQueue; + +private: + void + DescribeGCThing(bool aIsMarked, JS::GCCellPtr aThing, + nsCycleCollectionTraversalCallback& aCb) const; + + virtual bool + DescribeCustomObjects(JSObject* aObject, const js::Class* aClasp, + char (&aName)[72]) const + { + return false; // We did nothing. + } + + void + NoteGCThingJSChildren(JS::GCCellPtr aThing, + nsCycleCollectionTraversalCallback& aCb) const; + + void + NoteGCThingXPCOMChildren(const js::Class* aClasp, JSObject* aObj, + nsCycleCollectionTraversalCallback& aCb) const; + + virtual bool + NoteCustomGCThingXPCOMChildren(const js::Class* aClasp, JSObject* aObj, + nsCycleCollectionTraversalCallback& aCb) const + { + return false; // We did nothing. + } + + enum TraverseSelect { + TRAVERSE_CPP, + TRAVERSE_FULL + }; + + void + TraverseGCThing(TraverseSelect aTs, JS::GCCellPtr aThing, + nsCycleCollectionTraversalCallback& aCb); + + void + TraverseZone(JS::Zone* aZone, nsCycleCollectionTraversalCallback& aCb); + + static void + TraverseObjectShim(void* aData, JS::GCCellPtr aThing); + + void TraverseNativeRoots(nsCycleCollectionNoteRootCallback& aCb); + + static void TraceBlackJS(JSTracer* aTracer, void* aData); + static void TraceGrayJS(JSTracer* aTracer, void* aData); + static void GCCallback(JSContext* aContext, JSGCStatus aStatus, void* aData); + static void GCSliceCallback(JSContext* aContext, JS::GCProgress aProgress, + const JS::GCDescription& aDesc); + static void GCNurseryCollectionCallback(JSContext* aContext, + JS::GCNurseryProgress aProgress, + JS::gcreason::Reason aReason); + static void OutOfMemoryCallback(JSContext* aContext, void* aData); + static void LargeAllocationFailureCallback(void* aData); + static bool ContextCallback(JSContext* aCx, unsigned aOperation, + void* aData); + static JSObject* GetIncumbentGlobalCallback(JSContext* aCx); + static bool EnqueuePromiseJobCallback(JSContext* aCx, + JS::HandleObject aJob, + JS::HandleObject aAllocationSite, + JS::HandleObject aIncumbentGlobal, + void* aData); +#ifdef SPIDERMONKEY_PROMISE + static void PromiseRejectionTrackerCallback(JSContext* aCx, + JS::HandleObject aPromise, + PromiseRejectionHandlingState state, + void* aData); +#endif // SPIDERMONKEY_PROMISE + + virtual void TraceNativeBlackRoots(JSTracer* aTracer) { }; + void TraceNativeGrayRoots(JSTracer* aTracer); + + void AfterProcessMicrotask(uint32_t aRecursionDepth); +public: + void ProcessStableStateQueue(); +private: + void ProcessMetastableStateQueue(uint32_t aRecursionDepth); + +public: + enum DeferredFinalizeType { + FinalizeIncrementally, + FinalizeNow, + }; + + void FinalizeDeferredThings(DeferredFinalizeType aType); + + // Two conditions, JSOutOfMemory and JSLargeAllocationFailure, are noted in + // crash reports. Here are the values that can appear in the reports: + enum class OOMState : uint32_t { + // The condition has never happened. No entry appears in the crash report. + OK, + + // We are currently reporting the given condition. + // + // Suppose a crash report contains "JSLargeAllocationFailure: + // Reporting". This means we crashed while executing memory-pressure + // observers, trying to shake loose some memory. The large allocation in + // question did not return null: it is still on the stack. Had we not + // crashed, it would have been retried. + Reporting, + + // The condition has been reported since the last GC. + // + // If a crash report contains "JSOutOfMemory: Reported", that means a small + // allocation failed, and then we crashed, probably due to buggy + // error-handling code that ran after allocation returned null. + // + // This contrasts with "Reporting" which means that no error-handling code + // had executed yet. + Reported, + + // The condition has happened, but a GC cycle ended since then. + // + // GC is taken as a proxy for "we've been banging on the heap a good bit + // now and haven't crashed; the OOM was probably handled correctly". + Recovered + }; + +private: + void AnnotateAndSetOutOfMemory(OOMState* aStatePtr, OOMState aNewState); + void OnGC(JSGCStatus aStatus); + void OnOutOfMemory(); + void OnLargeAllocationFailure(); + +public: + void AddJSHolder(void* aHolder, nsScriptObjectTracer* aTracer); + void RemoveJSHolder(void* aHolder); +#ifdef DEBUG + bool IsJSHolder(void* aHolder); + void AssertNoObjectsToTrace(void* aPossibleJSHolder); +#endif + + already_AddRefed<nsIException> GetPendingException() const; + void SetPendingException(nsIException* aException); + + std::queue<nsCOMPtr<nsIRunnable>>& GetPromiseMicroTaskQueue(); + std::queue<nsCOMPtr<nsIRunnable>>& GetDebuggerPromiseMicroTaskQueue(); + + nsCycleCollectionParticipant* GCThingParticipant(); + nsCycleCollectionParticipant* ZoneParticipant(); + + nsresult TraverseRoots(nsCycleCollectionNoteRootCallback& aCb); + virtual bool UsefulToMergeZones() const; + void FixWeakMappingGrayBits() const; + bool AreGCGrayBitsValid() const; + void GarbageCollect(uint32_t aReason) const; + + void NurseryWrapperAdded(nsWrapperCache* aCache); + void NurseryWrapperPreserved(JSObject* aWrapper); + void JSObjectsTenured(); + + void DeferredFinalize(DeferredFinalizeAppendFunction aAppendFunc, + DeferredFinalizeFunction aFunc, + void* aThing); + void DeferredFinalize(nsISupports* aSupports); + + void DumpJSHeap(FILE* aFile); + + virtual void PrepareForForgetSkippable() = 0; + virtual void BeginCycleCollectionCallback() = 0; + virtual void EndCycleCollectionCallback(CycleCollectorResults& aResults) = 0; + virtual void DispatchDeferredDeletion(bool aContinuation, bool aPurge = false) = 0; + + JSContext* Context() const + { + MOZ_ASSERT(mJSContext); + return mJSContext; + } + + JS::RootingContext* RootingCx() const + { + MOZ_ASSERT(mJSContext); + return JS::RootingContext::get(mJSContext); + } + + bool MicroTaskCheckpointDisabled() const + { + return mDisableMicroTaskCheckpoint; + } + + void DisableMicroTaskCheckpoint(bool aDisable) + { + mDisableMicroTaskCheckpoint = aDisable; + } + + class MOZ_RAII AutoDisableMicroTaskCheckpoint + { + public: + AutoDisableMicroTaskCheckpoint() + : mCCJSCX(CycleCollectedJSContext::Get()) + { + mOldValue = mCCJSCX->MicroTaskCheckpointDisabled(); + mCCJSCX->DisableMicroTaskCheckpoint(true); + } + + ~AutoDisableMicroTaskCheckpoint() + { + mCCJSCX->DisableMicroTaskCheckpoint(mOldValue); + } + + CycleCollectedJSContext* mCCJSCX; + bool mOldValue; + }; + +protected: + JSContext* MaybeContext() const { return mJSContext; } + +public: + // nsThread entrypoints + virtual void BeforeProcessTask(bool aMightBlock) { }; + virtual void AfterProcessTask(uint32_t aRecursionDepth); + + // microtask processor entry point + void AfterProcessMicrotask(); + + uint32_t RecursionDepth(); + + // Run in stable state (call through nsContentUtils) + void RunInStableState(already_AddRefed<nsIRunnable>&& aRunnable); + // This isn't in the spec at all yet, but this gets the behavior we want for IDB. + // Runs after the current microtask completes. + void RunInMetastableState(already_AddRefed<nsIRunnable>&& aRunnable); + + // Get the current thread's CycleCollectedJSContext. Returns null if there + // isn't one. + static CycleCollectedJSContext* Get(); + + // Add aZone to the set of zones waiting for a GC. + void AddZoneWaitingForGC(JS::Zone* aZone) + { + mZonesWaitingForGC.PutEntry(aZone); + } + + // Prepare any zones for GC that have been passed to AddZoneWaitingForGC() + // since the last GC or since the last call to PrepareWaitingZonesForGC(), + // whichever was most recent. If there were no such zones, prepare for a + // full GC. + void PrepareWaitingZonesForGC(); + + // Queue an async microtask to the current main or worker thread. + virtual void DispatchToMicroTask(already_AddRefed<nsIRunnable> aRunnable); + + // Storage for watching rejected promises waiting for some client to + // consume their rejection. +#ifdef SPIDERMONKEY_PROMISE + // Promises in this list have been rejected in the last turn of the + // event loop without the rejection being handled. + // Note that this can contain nullptrs in place of promises removed because + // they're consumed before it'd be reported. + JS::PersistentRooted<JS::GCVector<JSObject*, 0, js::SystemAllocPolicy>> mUncaughtRejections; + + // Promises in this list have previously been reported as rejected + // (because they were in the above list), but the rejection was handled + // in the last turn of the event loop. + JS::PersistentRooted<JS::GCVector<JSObject*, 0, js::SystemAllocPolicy>> mConsumedRejections; +#else + // We store values as `nsISupports` to avoid adding compile-time dependencies + // from xpcom to dom/promise, but they can really only have a single concrete + // type. + nsTArray<nsCOMPtr<nsISupports /* Promise */>> mUncaughtRejections; + nsTArray<nsCOMPtr<nsISupports /* Promise */ >> mConsumedRejections; +#endif // SPIDERMONKEY_PROMISE + nsTArray<nsCOMPtr<nsISupports /* UncaughtRejectionObserver */ >> mUncaughtRejectionObservers; + +private: + JSGCThingParticipant mGCThingCycleCollectorGlobal; + + JSZoneParticipant mJSZoneCycleCollectorGlobal; + + JSContext* mJSContext; + + JS::GCSliceCallback mPrevGCSliceCallback; + JS::GCNurseryCollectionCallback mPrevGCNurseryCollectionCallback; + + nsDataHashtable<nsPtrHashKey<void>, nsScriptObjectTracer*> mJSHolders; + + typedef nsDataHashtable<nsFuncPtrHashKey<DeferredFinalizeFunction>, void*> + DeferredFinalizerTable; + DeferredFinalizerTable mDeferredFinalizerTable; + + RefPtr<IncrementalFinalizeRunnable> mFinalizeRunnable; + + nsCOMPtr<nsIException> mPendingException; + nsThread* mOwningThread; // Manual refcounting to avoid include hell. + + struct RunInMetastableStateData + { + nsCOMPtr<nsIRunnable> mRunnable; + uint32_t mRecursionDepth; + }; + + nsTArray<nsCOMPtr<nsIRunnable>> mStableStateEvents; + nsTArray<RunInMetastableStateData> mMetastableStateEvents; + uint32_t mBaseRecursionDepth; + bool mDoingStableStates; + + bool mDisableMicroTaskCheckpoint; + + OOMState mOutOfMemoryState; + OOMState mLargeAllocationFailureState; + + static const size_t kSegmentSize = 512; + SegmentedVector<nsWrapperCache*, kSegmentSize, InfallibleAllocPolicy> + mNurseryObjects; + SegmentedVector<JS::PersistentRooted<JSObject*>, kSegmentSize, + InfallibleAllocPolicy> + mPreservedNurseryObjects; + + nsTHashtable<nsPtrHashKey<JS::Zone>> mZonesWaitingForGC; + + struct EnvironmentPreparer : public js::ScriptEnvironmentPreparer { + void invoke(JS::HandleObject scope, Closure& closure) override; + }; + EnvironmentPreparer mEnvironmentPreparer; +}; + +void TraceScriptHolder(nsISupports* aHolder, JSTracer* aTracer); + +// Returns true if the JS::TraceKind is one the cycle collector cares about. +inline bool AddToCCKind(JS::TraceKind aKind) +{ + return aKind == JS::TraceKind::Object || aKind == JS::TraceKind::Script || aKind == JS::TraceKind::Scope; +} + +bool +GetBuildId(JS::BuildIdCharVector* aBuildID); + +} // namespace mozilla + +#endif // mozilla_CycleCollectedJSContext_h__ diff --git a/xpcom/base/Debug.cpp b/xpcom/base/Debug.cpp new file mode 100644 index 000000000..cc5272da0 --- /dev/null +++ b/xpcom/base/Debug.cpp @@ -0,0 +1,23 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Debug.h" + +#ifdef XP_WIN +#include <windows.h> +#endif + +#ifdef XP_WIN + +void +mozilla::PrintToDebugger(const char* aStr) +{ + if (::IsDebuggerPresent()) { + ::OutputDebugStringA(aStr); + } +} + +#endif diff --git a/xpcom/base/Debug.h b/xpcom/base/Debug.h new file mode 100644 index 000000000..2479799ae --- /dev/null +++ b/xpcom/base/Debug.h @@ -0,0 +1,21 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_Debug_h__ +#define mozilla_Debug_h__ + +namespace mozilla { + +#ifdef XP_WIN + +// Print aStr to a debugger if the debugger is attached. +void PrintToDebugger(const char* aStr); + +#endif + +} // namespace mozilla + +#endif // mozilla_Debug_h__ diff --git a/xpcom/base/DebuggerOnGCRunnable.cpp b/xpcom/base/DebuggerOnGCRunnable.cpp new file mode 100644 index 000000000..96f4cddcb --- /dev/null +++ b/xpcom/base/DebuggerOnGCRunnable.cpp @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/DebuggerOnGCRunnable.h" + +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/CycleCollectedJSContext.h" +#include "mozilla/Move.h" +#include "js/Debug.h" + +namespace mozilla { + +/* static */ nsresult +DebuggerOnGCRunnable::Enqueue(JSContext* aCx, const JS::GCDescription& aDesc) +{ + auto gcEvent = aDesc.toGCEvent(aCx); + if (!gcEvent) { + return NS_ERROR_OUT_OF_MEMORY; + } + + RefPtr<DebuggerOnGCRunnable> runOnGC = + new DebuggerOnGCRunnable(Move(gcEvent)); + return NS_DispatchToCurrentThread(runOnGC); +} + +NS_IMETHODIMP +DebuggerOnGCRunnable::Run() +{ + AutoJSAPI jsapi; + jsapi.Init(); + if (!JS::dbg::FireOnGarbageCollectionHook(jsapi.cx(), Move(mGCData))) { + return NS_ERROR_OUT_OF_MEMORY; + } + return NS_OK; +} + +nsresult +DebuggerOnGCRunnable::Cancel() +{ + mGCData = nullptr; + return NS_OK; +} + +} // namespace mozilla diff --git a/xpcom/base/DebuggerOnGCRunnable.h b/xpcom/base/DebuggerOnGCRunnable.h new file mode 100644 index 000000000..8f9621613 --- /dev/null +++ b/xpcom/base/DebuggerOnGCRunnable.h @@ -0,0 +1,35 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_DebuggerOnGCRunnable_h +#define mozilla_DebuggerOnGCRunnable_h + +#include "nsThreadUtils.h" +#include "js/GCAPI.h" +#include "mozilla/Move.h" +#include "mozilla/UniquePtr.h" + +namespace mozilla { + +// Runnable to fire the SpiderMonkey Debugger API's onGarbageCollection hook. +class DebuggerOnGCRunnable : public CancelableRunnable +{ + JS::dbg::GarbageCollectionEvent::Ptr mGCData; + + explicit DebuggerOnGCRunnable(JS::dbg::GarbageCollectionEvent::Ptr&& aGCData) + : mGCData(Move(aGCData)) + { } + +public: + static nsresult Enqueue(JSContext* aCx, const JS::GCDescription& aDesc); + + NS_DECL_NSIRUNNABLE + nsresult Cancel() override; +}; + +} // namespace mozilla + +#endif // ifdef mozilla_dom_DebuggerOnGCRunnable_h diff --git a/xpcom/base/DeferredFinalize.cpp b/xpcom/base/DeferredFinalize.cpp new file mode 100644 index 000000000..b9a71b81c --- /dev/null +++ b/xpcom/base/DeferredFinalize.cpp @@ -0,0 +1,28 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/DeferredFinalize.h" + +#include "mozilla/Assertions.h" +#include "mozilla/CycleCollectedJSContext.h" + +void +mozilla::DeferredFinalize(nsISupports* aSupports) +{ + CycleCollectedJSContext* cx = CycleCollectedJSContext::Get(); + MOZ_ASSERT(cx, "Should have a CycleCollectedJSContext by now"); + cx->DeferredFinalize(aSupports); +} + +void +mozilla::DeferredFinalize(DeferredFinalizeAppendFunction aAppendFunc, + DeferredFinalizeFunction aFunc, + void* aThing) +{ + CycleCollectedJSContext* cx = CycleCollectedJSContext::Get(); + MOZ_ASSERT(cx, "Should have a CycleCollectedJSContext by now"); + cx->DeferredFinalize(aAppendFunc, aFunc, aThing); +} diff --git a/xpcom/base/DeferredFinalize.h b/xpcom/base/DeferredFinalize.h new file mode 100644 index 000000000..7d9c58881 --- /dev/null +++ b/xpcom/base/DeferredFinalize.h @@ -0,0 +1,33 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_DeferredFinalize_h +#define mozilla_DeferredFinalize_h + +class nsISupports; + +namespace mozilla { + +// Called back from DeferredFinalize. Should add 'thing' to the array of smart +// pointers in 'pointers', creating the array if 'pointers' is null, and return +// the array. +typedef void* (*DeferredFinalizeAppendFunction)(void* aPointers, void* aThing); + +// Called to finalize a number of objects. Slice is the number of objects to +// finalize. The return value indicates whether it finalized all objects in the +// buffer. If it returns true, the function will not be called again, so the +// function should free aData. +typedef bool (*DeferredFinalizeFunction)(uint32_t aSlice, void* aData); + +void DeferredFinalize(DeferredFinalizeAppendFunction aAppendFunc, + DeferredFinalizeFunction aFunc, + void* aThing); + +void DeferredFinalize(nsISupports* aSupports); + +} // namespace mozilla + +#endif // mozilla_DeferredFinalize_h diff --git a/xpcom/base/ErrorList.h b/xpcom/base/ErrorList.h new file mode 100644 index 000000000..cfa461fe4 --- /dev/null +++ b/xpcom/base/ErrorList.h @@ -0,0 +1,1026 @@ +// IWYU pragma: private, include "nsError.h" +/* Helper file for nsError.h, via preprocessor magic */ + /* Standard "it worked" return value */ + ERROR(NS_OK, 0), + + /* ======================================================================= */ + /* Core errors, not part of any modules */ + /* ======================================================================= */ + ERROR(NS_ERROR_BASE, 0xC1F30000), + /* Returned when an instance is not initialized */ + ERROR(NS_ERROR_NOT_INITIALIZED, NS_ERROR_BASE + 1), + /* Returned when an instance is already initialized */ + ERROR(NS_ERROR_ALREADY_INITIALIZED, NS_ERROR_BASE + 2), + /* Returned by a not implemented function */ + ERROR(NS_ERROR_NOT_IMPLEMENTED, 0x80004001), + /* Returned when a given interface is not supported. */ + ERROR(NS_NOINTERFACE, 0x80004002), + ERROR(NS_ERROR_NO_INTERFACE, NS_NOINTERFACE), + /* Returned when a function aborts */ + ERROR(NS_ERROR_ABORT, 0x80004004), + /* Returned when a function fails */ + ERROR(NS_ERROR_FAILURE, 0x80004005), + /* Returned when an unexpected error occurs */ + ERROR(NS_ERROR_UNEXPECTED, 0x8000ffff), + /* Returned when a memory allocation fails */ + ERROR(NS_ERROR_OUT_OF_MEMORY, 0x8007000e), + /* Returned when an illegal value is passed */ + ERROR(NS_ERROR_ILLEGAL_VALUE, 0x80070057), + ERROR(NS_ERROR_INVALID_ARG, NS_ERROR_ILLEGAL_VALUE), + ERROR(NS_ERROR_INVALID_POINTER, NS_ERROR_INVALID_ARG), + ERROR(NS_ERROR_NULL_POINTER, NS_ERROR_INVALID_ARG), + /* Returned when a class doesn't allow aggregation */ + ERROR(NS_ERROR_NO_AGGREGATION, 0x80040110), + /* Returned when an operation can't complete due to an unavailable resource */ + ERROR(NS_ERROR_NOT_AVAILABLE, 0x80040111), + /* Returned when a class is not registered */ + ERROR(NS_ERROR_FACTORY_NOT_REGISTERED, 0x80040154), + /* Returned when a class cannot be registered, but may be tried again later */ + ERROR(NS_ERROR_FACTORY_REGISTER_AGAIN, 0x80040155), + /* Returned when a dynamically loaded factory couldn't be found */ + ERROR(NS_ERROR_FACTORY_NOT_LOADED, 0x800401f8), + /* Returned when a factory doesn't support signatures */ + ERROR(NS_ERROR_FACTORY_NO_SIGNATURE_SUPPORT, NS_ERROR_BASE + 0x101), + /* Returned when a factory already is registered */ + ERROR(NS_ERROR_FACTORY_EXISTS, NS_ERROR_BASE + 0x100), + + + /* ======================================================================= */ + /* 1: NS_ERROR_MODULE_XPCOM */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_XPCOM + /* Result codes used by nsIVariant */ + ERROR(NS_ERROR_CANNOT_CONVERT_DATA, FAILURE(1)), + ERROR(NS_ERROR_OBJECT_IS_IMMUTABLE, FAILURE(2)), + ERROR(NS_ERROR_LOSS_OF_SIGNIFICANT_DATA, FAILURE(3)), + /* Result code used by nsIThreadManager */ + ERROR(NS_ERROR_NOT_SAME_THREAD, FAILURE(4)), + /* Various operations are not permitted during XPCOM shutdown and will fail + * with this exception. */ + ERROR(NS_ERROR_ILLEGAL_DURING_SHUTDOWN, FAILURE(30)), + ERROR(NS_ERROR_SERVICE_NOT_AVAILABLE, FAILURE(22)), + + ERROR(NS_SUCCESS_LOSS_OF_INSIGNIFICANT_DATA, SUCCESS(1)), + /* Used by nsCycleCollectionParticipant */ + ERROR(NS_SUCCESS_INTERRUPTED_TRAVERSE, SUCCESS(2)), + /* DEPRECATED */ + ERROR(NS_ERROR_SERVICE_NOT_FOUND, SUCCESS(22)), + /* DEPRECATED */ + ERROR(NS_ERROR_SERVICE_IN_USE, SUCCESS(23)), +#undef MODULE + + + /* ======================================================================= */ + /* 2: NS_ERROR_MODULE_BASE */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_BASE + /* I/O Errors */ + + /* Stream closed */ + ERROR(NS_BASE_STREAM_CLOSED, FAILURE(2)), + /* Error from the operating system */ + ERROR(NS_BASE_STREAM_OSERROR, FAILURE(3)), + /* Illegal arguments */ + ERROR(NS_BASE_STREAM_ILLEGAL_ARGS, FAILURE(4)), + /* For unichar streams */ + ERROR(NS_BASE_STREAM_NO_CONVERTER, FAILURE(5)), + /* For unichar streams */ + ERROR(NS_BASE_STREAM_BAD_CONVERSION, FAILURE(6)), + ERROR(NS_BASE_STREAM_WOULD_BLOCK, FAILURE(7)), +#undef MODULE + + + /* ======================================================================= */ + /* 3: NS_ERROR_MODULE_GFX */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_GFX + /* no printer available (e.g. cannot find _any_ printer) */ + ERROR(NS_ERROR_GFX_PRINTER_NO_PRINTER_AVAILABLE, FAILURE(1)), + /* _specified_ (by name) printer not found */ + ERROR(NS_ERROR_GFX_PRINTER_NAME_NOT_FOUND, FAILURE(2)), + /* print-to-file: could not open output file */ + ERROR(NS_ERROR_GFX_PRINTER_COULD_NOT_OPEN_FILE, FAILURE(3)), + /* print: starting document */ + ERROR(NS_ERROR_GFX_PRINTER_STARTDOC, FAILURE(4)), + /* print: ending document */ + ERROR(NS_ERROR_GFX_PRINTER_ENDDOC, FAILURE(5)), + /* print: starting page */ + ERROR(NS_ERROR_GFX_PRINTER_STARTPAGE, FAILURE(6)), + /* The document is still being loaded */ + ERROR(NS_ERROR_GFX_PRINTER_DOC_IS_BUSY, FAILURE(7)), + + /* Font cmap is strangely structured - avoid this font! */ + ERROR(NS_ERROR_GFX_CMAP_MALFORMED, FAILURE(51)), +#undef MODULE + + + /* ======================================================================= */ + /* 4: NS_ERROR_MODULE_WIDGET */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_WIDGET + /* Used by: + * - nsIWidget::NotifyIME() + * - nsIWidget::OnWindowedPluginKeyEvent() + * Returned when the notification or the event is handled and it's consumed + * by somebody. */ + ERROR(NS_SUCCESS_EVENT_CONSUMED, SUCCESS(1)), + /* Used by: + * - nsIWidget::OnWindowedPluginKeyEvent() + * Returned when the event is handled correctly but the result will be + * notified asynchronously. */ + ERROR(NS_SUCCESS_EVENT_HANDLED_ASYNCHRONOUSLY, SUCCESS(2)), +#undef MODULE + + + /* ======================================================================= */ + /* 6: NS_ERROR_MODULE_NETWORK */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_NETWORK + /* General async request error codes: + * + * These error codes are commonly passed through callback methods to indicate + * the status of some requested async request. + * + * For example, see nsIRequestObserver::onStopRequest. + */ + + /* The async request completed successfully. */ + ERROR(NS_BINDING_SUCCEEDED, NS_OK), + + /* The async request failed for some unknown reason. */ + ERROR(NS_BINDING_FAILED, FAILURE(1)), + /* The async request failed because it was aborted by some user action. */ + ERROR(NS_BINDING_ABORTED, FAILURE(2)), + /* The async request has been "redirected" to a different async request. + * (e.g., an HTTP redirect occurred). + * + * This error code is used with load groups to notify the load group observer + * when a request in the load group is redirected to another request. */ + ERROR(NS_BINDING_REDIRECTED, FAILURE(3)), + /* The async request has been "retargeted" to a different "handler." + * + * This error code is used with load groups to notify the load group observer + * when a request in the load group is removed from the load group and added + * to a different load group. */ + ERROR(NS_BINDING_RETARGETED, FAILURE(4)), + + /* Miscellaneous error codes: These errors are not typically passed via + * onStopRequest. */ + + /* The URI is malformed. */ + ERROR(NS_ERROR_MALFORMED_URI, FAILURE(10)), + /* The requested action could not be completed while the object is busy. + * Implementations of nsIChannel::asyncOpen will commonly return this error + * if the channel has already been opened (and has not yet been closed). */ + ERROR(NS_ERROR_IN_PROGRESS, FAILURE(15)), + /* Returned from nsIChannel::asyncOpen to indicate that OnDataAvailable will + * not be called because there is no content available. This is used by + * helper app style protocols (e.g., mailto). XXX perhaps this should be a + * success code. */ + ERROR(NS_ERROR_NO_CONTENT, FAILURE(17)), + /* The URI scheme corresponds to an unknown protocol handler. */ + ERROR(NS_ERROR_UNKNOWN_PROTOCOL, FAILURE(18)), + /* The content encoding of the source document was incorrect, for example + * returning a plain HTML document advertised as Content-Encoding: gzip */ + ERROR(NS_ERROR_INVALID_CONTENT_ENCODING, FAILURE(27)), + /* A transport level corruption was found in the source document. for example + * a document with a calculated checksum that does not match the Content-MD5 + * http header. */ + ERROR(NS_ERROR_CORRUPTED_CONTENT, FAILURE(29)), + /* A content signature verification failed for some reason. This can be either + * an actual verification error, or any other error that led to the fact that + * a content signature that was expected couldn't be verified. */ + ERROR(NS_ERROR_INVALID_SIGNATURE, FAILURE(58)), + /* While parsing for the first component of a header field using syntax as in + * Content-Disposition or Content-Type, the first component was found to be + * empty, such as in: Content-Disposition: ; filename=foo */ + ERROR(NS_ERROR_FIRST_HEADER_FIELD_COMPONENT_EMPTY, FAILURE(34)), + /* Returned from nsIChannel::asyncOpen when trying to open the channel again + * (reopening is not supported). */ + ERROR(NS_ERROR_ALREADY_OPENED, FAILURE(73)), + + /* Connectivity error codes: */ + + /* The connection is already established. XXX unused - consider removing. */ + ERROR(NS_ERROR_ALREADY_CONNECTED, FAILURE(11)), + /* The connection does not exist. XXX unused - consider removing. */ + ERROR(NS_ERROR_NOT_CONNECTED, FAILURE(12)), + /* The connection attempt failed, for example, because no server was + * listening at specified host:port. */ + ERROR(NS_ERROR_CONNECTION_REFUSED, FAILURE(13)), + /* The connection was lost due to a timeout error. */ + ERROR(NS_ERROR_NET_TIMEOUT, FAILURE(14)), + /* The requested action could not be completed while the networking library + * is in the offline state. */ + ERROR(NS_ERROR_OFFLINE, FAILURE(16)), + /* The requested action was prohibited because it would have caused the + * networking library to establish a connection to an unsafe or otherwise + * banned port. */ + ERROR(NS_ERROR_PORT_ACCESS_NOT_ALLOWED, FAILURE(19)), + /* The connection was established, but no data was ever received. */ + ERROR(NS_ERROR_NET_RESET, FAILURE(20)), + /* The connection was established, but the data transfer was interrupted. */ + ERROR(NS_ERROR_NET_INTERRUPT, FAILURE(71)), + /* The connection attempt to a proxy failed. */ + ERROR(NS_ERROR_PROXY_CONNECTION_REFUSED, FAILURE(72)), + /* A transfer was only partially done when it completed. */ + ERROR(NS_ERROR_NET_PARTIAL_TRANSFER, FAILURE(76)), + /* HTTP/2 detected invalid TLS configuration */ + ERROR(NS_ERROR_NET_INADEQUATE_SECURITY, FAILURE(82)), + + /* XXX really need to better rationalize these error codes. are consumers of + * necko really expected to know how to discern the meaning of these?? */ + /* This request is not resumable, but it was tried to resume it, or to + * request resume-specific data. */ + ERROR(NS_ERROR_NOT_RESUMABLE, FAILURE(25)), + /* The request failed as a result of a detected redirection loop. */ + ERROR(NS_ERROR_REDIRECT_LOOP, FAILURE(31)), + /* It was attempted to resume the request, but the entity has changed in the + * meantime. */ + ERROR(NS_ERROR_ENTITY_CHANGED, FAILURE(32)), + /* The request failed because the content type returned by the server was not + * a type expected by the channel (for nested channels such as the JAR + * channel). */ + ERROR(NS_ERROR_UNSAFE_CONTENT_TYPE, FAILURE(74)), + /* The request failed because the user tried to access to a remote XUL + * document from a website that is not in its white-list. */ + ERROR(NS_ERROR_REMOTE_XUL, FAILURE(75)), + /* The request resulted in an error page being displayed. */ + ERROR(NS_ERROR_LOAD_SHOWED_ERRORPAGE, FAILURE(77)), + + + /* FTP specific error codes: */ + + ERROR(NS_ERROR_FTP_LOGIN, FAILURE(21)), + ERROR(NS_ERROR_FTP_CWD, FAILURE(22)), + ERROR(NS_ERROR_FTP_PASV, FAILURE(23)), + ERROR(NS_ERROR_FTP_PWD, FAILURE(24)), + ERROR(NS_ERROR_FTP_LIST, FAILURE(28)), + + /* DNS specific error codes: */ + + /* The lookup of a hostname failed. This generally refers to the hostname + * from the URL being loaded. */ + ERROR(NS_ERROR_UNKNOWN_HOST, FAILURE(30)), + /* A low or medium priority DNS lookup failed because the pending queue was + * already full. High priorty (the default) always makes room */ + ERROR(NS_ERROR_DNS_LOOKUP_QUEUE_FULL, FAILURE(33)), + /* The lookup of a proxy hostname failed. If a channel is configured to + * speak to a proxy server, then it will generate this error if the proxy + * hostname cannot be resolved. */ + ERROR(NS_ERROR_UNKNOWN_PROXY_HOST, FAILURE(42)), + + + /* Socket specific error codes: */ + + /* The specified socket type does not exist. */ + ERROR(NS_ERROR_UNKNOWN_SOCKET_TYPE, FAILURE(51)), + /* The specified socket type could not be created. */ + ERROR(NS_ERROR_SOCKET_CREATE_FAILED, FAILURE(52)), + /* The operating system doesn't support the given type of address. */ + ERROR(NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED, FAILURE(53)), + /* The address to which we tried to bind the socket was busy. */ + ERROR(NS_ERROR_SOCKET_ADDRESS_IN_USE, FAILURE(54)), + + /* Cache specific error codes: */ + ERROR(NS_ERROR_CACHE_KEY_NOT_FOUND, FAILURE(61)), + ERROR(NS_ERROR_CACHE_DATA_IS_STREAM, FAILURE(62)), + ERROR(NS_ERROR_CACHE_DATA_IS_NOT_STREAM, FAILURE(63)), + ERROR(NS_ERROR_CACHE_WAIT_FOR_VALIDATION, FAILURE(64)), + ERROR(NS_ERROR_CACHE_ENTRY_DOOMED, FAILURE(65)), + ERROR(NS_ERROR_CACHE_READ_ACCESS_DENIED, FAILURE(66)), + ERROR(NS_ERROR_CACHE_WRITE_ACCESS_DENIED, FAILURE(67)), + ERROR(NS_ERROR_CACHE_IN_USE, FAILURE(68)), + /* Error passed through onStopRequest if the document could not be fetched + * from the cache. */ + ERROR(NS_ERROR_DOCUMENT_NOT_CACHED, FAILURE(70)), + + /* Effective TLD Service specific error codes: */ + + /* The requested number of domain levels exceeds those present in the host + * string. */ + ERROR(NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS, FAILURE(80)), + /* The host string is an IP address. */ + ERROR(NS_ERROR_HOST_IS_IP_ADDRESS, FAILURE(81)), + + + /* StreamLoader specific result codes: */ + + /* Result code returned by nsIStreamLoaderObserver to indicate that the + * observer is taking over responsibility for the data buffer, and the loader + * should NOT free it. */ + ERROR(NS_SUCCESS_ADOPTED_DATA, SUCCESS(90)), + + /* FTP */ + ERROR(NS_NET_STATUS_BEGIN_FTP_TRANSACTION, SUCCESS(27)), + ERROR(NS_NET_STATUS_END_FTP_TRANSACTION, SUCCESS(28)), + + /* This success code may be returned by nsIAuthModule::getNextToken to + * indicate that the authentication is finished and thus there's no need + * to call getNextToken again. */ + ERROR(NS_SUCCESS_AUTH_FINISHED, SUCCESS(40)), + + /* These are really not "results", they're statuses, used by nsITransport and + * friends. This is abuse of nsresult, but we'll put up with it for now. */ + /* nsITransport */ + ERROR(NS_NET_STATUS_READING, FAILURE(8)), + ERROR(NS_NET_STATUS_WRITING, FAILURE(9)), + + /* nsISocketTransport */ + ERROR(NS_NET_STATUS_RESOLVING_HOST, FAILURE(3)), + ERROR(NS_NET_STATUS_RESOLVED_HOST, FAILURE(11)), + ERROR(NS_NET_STATUS_CONNECTING_TO, FAILURE(7)), + ERROR(NS_NET_STATUS_CONNECTED_TO, FAILURE(4)), + ERROR(NS_NET_STATUS_SENDING_TO, FAILURE(5)), + ERROR(NS_NET_STATUS_WAITING_FOR, FAILURE(10)), + ERROR(NS_NET_STATUS_RECEIVING_FROM, FAILURE(6)), + + /* nsIInterceptedChannel */ + /* Generic error for non-specific failures during service worker interception */ + ERROR(NS_ERROR_INTERCEPTION_FAILED, FAILURE(100)), +#undef MODULE + + + /* ======================================================================= */ + /* 7: NS_ERROR_MODULE_PLUGINS */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_PLUGINS + ERROR(NS_ERROR_PLUGINS_PLUGINSNOTCHANGED, FAILURE(1000)), + ERROR(NS_ERROR_PLUGIN_DISABLED, FAILURE(1001)), + ERROR(NS_ERROR_PLUGIN_BLOCKLISTED, FAILURE(1002)), + ERROR(NS_ERROR_PLUGIN_TIME_RANGE_NOT_SUPPORTED, FAILURE(1003)), + ERROR(NS_ERROR_PLUGIN_CLICKTOPLAY, FAILURE(1004)), + ERROR(NS_PLUGIN_INIT_PENDING, SUCCESS(1005)), +#undef MODULE + + + /* ======================================================================= */ + /* 8: NS_ERROR_MODULE_LAYOUT */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_LAYOUT + /* Return code for nsITableLayout */ + ERROR(NS_TABLELAYOUT_CELL_NOT_FOUND, SUCCESS(0)), + /* Return code for nsFrame::GetNextPrevLineFromeBlockFrame */ + ERROR(NS_POSITION_BEFORE_TABLE, SUCCESS(3)), + /** Return codes for nsPresState::GetProperty() */ + /* Returned if the property exists */ + ERROR(NS_STATE_PROPERTY_EXISTS, NS_OK), + /* Returned if the property does not exist */ + ERROR(NS_STATE_PROPERTY_NOT_THERE, SUCCESS(5)), +#undef MODULE + + + /* ======================================================================= */ + /* 9: NS_ERROR_MODULE_HTMLPARSER */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_HTMLPARSER + ERROR(NS_ERROR_HTMLPARSER_CONTINUE, NS_OK), + + ERROR(NS_ERROR_HTMLPARSER_EOF, FAILURE(1000)), + ERROR(NS_ERROR_HTMLPARSER_UNKNOWN, FAILURE(1001)), + ERROR(NS_ERROR_HTMLPARSER_CANTPROPAGATE, FAILURE(1002)), + ERROR(NS_ERROR_HTMLPARSER_CONTEXTMISMATCH, FAILURE(1003)), + ERROR(NS_ERROR_HTMLPARSER_BADFILENAME, FAILURE(1004)), + ERROR(NS_ERROR_HTMLPARSER_BADURL, FAILURE(1005)), + ERROR(NS_ERROR_HTMLPARSER_INVALIDPARSERCONTEXT, FAILURE(1006)), + ERROR(NS_ERROR_HTMLPARSER_INTERRUPTED, FAILURE(1007)), + ERROR(NS_ERROR_HTMLPARSER_BLOCK, FAILURE(1008)), + ERROR(NS_ERROR_HTMLPARSER_BADTOKENIZER, FAILURE(1009)), + ERROR(NS_ERROR_HTMLPARSER_BADATTRIBUTE, FAILURE(1010)), + ERROR(NS_ERROR_HTMLPARSER_UNRESOLVEDDTD, FAILURE(1011)), + ERROR(NS_ERROR_HTMLPARSER_MISPLACEDTABLECONTENT, FAILURE(1012)), + ERROR(NS_ERROR_HTMLPARSER_BADDTD, FAILURE(1013)), + ERROR(NS_ERROR_HTMLPARSER_BADCONTEXT, FAILURE(1014)), + ERROR(NS_ERROR_HTMLPARSER_STOPPARSING, FAILURE(1015)), + ERROR(NS_ERROR_HTMLPARSER_UNTERMINATEDSTRINGLITERAL, FAILURE(1016)), + ERROR(NS_ERROR_HTMLPARSER_HIERARCHYTOODEEP, FAILURE(1017)), + ERROR(NS_ERROR_HTMLPARSER_FAKE_ENDTAG, FAILURE(1018)), + ERROR(NS_ERROR_HTMLPARSER_INVALID_COMMENT, FAILURE(1019)), + + ERROR(NS_HTMLTOKENS_NOT_AN_ENTITY, SUCCESS(2000)), + ERROR(NS_HTMLPARSER_VALID_META_CHARSET, SUCCESS(3000)), +#undef MODULE + + + /* ======================================================================= */ + /* 10: NS_ERROR_MODULE_RDF */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_RDF + /* Returned from nsIRDFDataSource::Assert() and Unassert() if the assertion + * (or unassertion was accepted by the datasource */ + ERROR(NS_RDF_ASSERTION_ACCEPTED, NS_OK), + /* Returned from nsIRDFCursor::Advance() if the cursor has no more + * elements to enumerate */ + ERROR(NS_RDF_CURSOR_EMPTY, SUCCESS(1)), + /* Returned from nsIRDFDataSource::GetSource() and GetTarget() if the + * source/target has no value */ + ERROR(NS_RDF_NO_VALUE, SUCCESS(2)), + /* Returned from nsIRDFDataSource::Assert() and Unassert() if the assertion + * (or unassertion) was rejected by the datasource; i.e., the datasource was + * not willing to record the statement. */ + ERROR(NS_RDF_ASSERTION_REJECTED, SUCCESS(3)), + /* Return this from rdfITripleVisitor to stop cycling */ + ERROR(NS_RDF_STOP_VISIT, SUCCESS(4)), +#undef MODULE + + + /* ======================================================================= */ + /* 11: NS_ERROR_MODULE_UCONV */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_UCONV + ERROR(NS_ERROR_UCONV_NOCONV, FAILURE(1)), + ERROR(NS_ERROR_UDEC_ILLEGALINPUT, FAILURE(14)), + + ERROR(NS_SUCCESS_USING_FALLBACK_LOCALE, SUCCESS(2)), + ERROR(NS_OK_UDEC_EXACTLENGTH, SUCCESS(11)), + ERROR(NS_OK_UDEC_MOREINPUT, SUCCESS(12)), + ERROR(NS_OK_UDEC_MOREOUTPUT, SUCCESS(13)), + ERROR(NS_OK_UDEC_NOBOMFOUND, SUCCESS(14)), + ERROR(NS_OK_UENC_EXACTLENGTH, SUCCESS(33)), + ERROR(NS_OK_UENC_MOREOUTPUT, SUCCESS(34)), + ERROR(NS_ERROR_UENC_NOMAPPING, SUCCESS(35)), + ERROR(NS_OK_UENC_MOREINPUT, SUCCESS(36)), + + /* BEGIN DEPRECATED */ + ERROR(NS_EXACT_LENGTH, NS_OK_UDEC_EXACTLENGTH), + ERROR(NS_PARTIAL_MORE_INPUT, NS_OK_UDEC_MOREINPUT), + ERROR(NS_PARTIAL_MORE_OUTPUT, NS_OK_UDEC_MOREOUTPUT), + ERROR(NS_ERROR_ILLEGAL_INPUT, NS_ERROR_UDEC_ILLEGALINPUT), + /* END DEPRECATED */ +#undef MODULE + + + /* ======================================================================= */ + /* 13: NS_ERROR_MODULE_FILES */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_FILES + ERROR(NS_ERROR_FILE_UNRECOGNIZED_PATH, FAILURE(1)), + ERROR(NS_ERROR_FILE_UNRESOLVABLE_SYMLINK, FAILURE(2)), + ERROR(NS_ERROR_FILE_EXECUTION_FAILED, FAILURE(3)), + ERROR(NS_ERROR_FILE_UNKNOWN_TYPE, FAILURE(4)), + ERROR(NS_ERROR_FILE_DESTINATION_NOT_DIR, FAILURE(5)), + ERROR(NS_ERROR_FILE_TARGET_DOES_NOT_EXIST, FAILURE(6)), + ERROR(NS_ERROR_FILE_COPY_OR_MOVE_FAILED, FAILURE(7)), + ERROR(NS_ERROR_FILE_ALREADY_EXISTS, FAILURE(8)), + ERROR(NS_ERROR_FILE_INVALID_PATH, FAILURE(9)), + ERROR(NS_ERROR_FILE_DISK_FULL, FAILURE(10)), + ERROR(NS_ERROR_FILE_CORRUPTED, FAILURE(11)), + ERROR(NS_ERROR_FILE_NOT_DIRECTORY, FAILURE(12)), + ERROR(NS_ERROR_FILE_IS_DIRECTORY, FAILURE(13)), + ERROR(NS_ERROR_FILE_IS_LOCKED, FAILURE(14)), + ERROR(NS_ERROR_FILE_TOO_BIG, FAILURE(15)), + ERROR(NS_ERROR_FILE_NO_DEVICE_SPACE, FAILURE(16)), + ERROR(NS_ERROR_FILE_NAME_TOO_LONG, FAILURE(17)), + ERROR(NS_ERROR_FILE_NOT_FOUND, FAILURE(18)), + ERROR(NS_ERROR_FILE_READ_ONLY, FAILURE(19)), + ERROR(NS_ERROR_FILE_DIR_NOT_EMPTY, FAILURE(20)), + ERROR(NS_ERROR_FILE_ACCESS_DENIED, FAILURE(21)), + + ERROR(NS_SUCCESS_FILE_DIRECTORY_EMPTY, SUCCESS(1)), + /* Result codes used by nsIDirectoryServiceProvider2 */ + ERROR(NS_SUCCESS_AGGREGATE_RESULT, SUCCESS(2)), +#undef MODULE + + + /* ======================================================================= */ + /* 14: NS_ERROR_MODULE_DOM */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_DOM + /* XXX If you add a new DOM error code, also add an error string to + * dom/base/domerr.msg */ + + /* Standard DOM error codes: http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html */ + ERROR(NS_ERROR_DOM_INDEX_SIZE_ERR, FAILURE(1)), + ERROR(NS_ERROR_DOM_HIERARCHY_REQUEST_ERR, FAILURE(3)), + ERROR(NS_ERROR_DOM_WRONG_DOCUMENT_ERR, FAILURE(4)), + ERROR(NS_ERROR_DOM_INVALID_CHARACTER_ERR, FAILURE(5)), + ERROR(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR, FAILURE(7)), + ERROR(NS_ERROR_DOM_NOT_FOUND_ERR, FAILURE(8)), + ERROR(NS_ERROR_DOM_NOT_SUPPORTED_ERR, FAILURE(9)), + ERROR(NS_ERROR_DOM_INUSE_ATTRIBUTE_ERR, FAILURE(10)), + ERROR(NS_ERROR_DOM_INVALID_STATE_ERR, FAILURE(11)), + ERROR(NS_ERROR_DOM_SYNTAX_ERR, FAILURE(12)), + ERROR(NS_ERROR_DOM_INVALID_MODIFICATION_ERR, FAILURE(13)), + ERROR(NS_ERROR_DOM_NAMESPACE_ERR, FAILURE(14)), + ERROR(NS_ERROR_DOM_INVALID_ACCESS_ERR, FAILURE(15)), + ERROR(NS_ERROR_DOM_TYPE_MISMATCH_ERR, FAILURE(17)), + ERROR(NS_ERROR_DOM_SECURITY_ERR, FAILURE(18)), + ERROR(NS_ERROR_DOM_NETWORK_ERR, FAILURE(19)), + ERROR(NS_ERROR_DOM_ABORT_ERR, FAILURE(20)), + ERROR(NS_ERROR_DOM_URL_MISMATCH_ERR, FAILURE(21)), + ERROR(NS_ERROR_DOM_QUOTA_EXCEEDED_ERR, FAILURE(22)), + ERROR(NS_ERROR_DOM_TIMEOUT_ERR, FAILURE(23)), + ERROR(NS_ERROR_DOM_INVALID_NODE_TYPE_ERR, FAILURE(24)), + ERROR(NS_ERROR_DOM_DATA_CLONE_ERR, FAILURE(25)), + /* XXX Should be JavaScript native errors */ + ERROR(NS_ERROR_TYPE_ERR, FAILURE(26)), + ERROR(NS_ERROR_RANGE_ERR, FAILURE(27)), + /* StringEncoding API errors from http://wiki.whatwg.org/wiki/StringEncoding */ + ERROR(NS_ERROR_DOM_ENCODING_NOT_SUPPORTED_ERR, FAILURE(28)), + ERROR(NS_ERROR_DOM_INVALID_POINTER_ERR, FAILURE(29)), + /* WebCrypto API errors from http://www.w3.org/TR/WebCryptoAPI/ */ + ERROR(NS_ERROR_DOM_UNKNOWN_ERR, FAILURE(30)), + ERROR(NS_ERROR_DOM_DATA_ERR, FAILURE(31)), + ERROR(NS_ERROR_DOM_OPERATION_ERR, FAILURE(32)), + /* https://heycam.github.io/webidl/#notallowederror */ + ERROR(NS_ERROR_DOM_NOT_ALLOWED_ERR, FAILURE(33)), + /* DOM error codes defined by us */ + ERROR(NS_ERROR_DOM_SECMAN_ERR, FAILURE(1001)), + ERROR(NS_ERROR_DOM_WRONG_TYPE_ERR, FAILURE(1002)), + ERROR(NS_ERROR_DOM_NOT_OBJECT_ERR, FAILURE(1003)), + ERROR(NS_ERROR_DOM_NOT_XPC_OBJECT_ERR, FAILURE(1004)), + ERROR(NS_ERROR_DOM_NOT_NUMBER_ERR, FAILURE(1005)), + ERROR(NS_ERROR_DOM_NOT_BOOLEAN_ERR, FAILURE(1006)), + ERROR(NS_ERROR_DOM_NOT_FUNCTION_ERR, FAILURE(1007)), + ERROR(NS_ERROR_DOM_TOO_FEW_PARAMETERS_ERR, FAILURE(1008)), + ERROR(NS_ERROR_DOM_BAD_DOCUMENT_DOMAIN, FAILURE(1009)), + ERROR(NS_ERROR_DOM_PROP_ACCESS_DENIED, FAILURE(1010)), + ERROR(NS_ERROR_DOM_XPCONNECT_ACCESS_DENIED, FAILURE(1011)), + ERROR(NS_ERROR_DOM_BAD_URI, FAILURE(1012)), + ERROR(NS_ERROR_DOM_RETVAL_UNDEFINED, FAILURE(1013)), + ERROR(NS_ERROR_DOM_QUOTA_REACHED, FAILURE(1014)), + ERROR(NS_ERROR_DOM_JS_EXCEPTION, FAILURE(1015)), + + /* A way to represent uncatchable exceptions */ + ERROR(NS_ERROR_UNCATCHABLE_EXCEPTION, FAILURE(1016)), + + /* An nsresult value to use in ErrorResult to indicate that we want to throw + a DOMException */ + ERROR(NS_ERROR_DOM_DOMEXCEPTION, FAILURE(1017)), + + /* An nsresult value to use in ErrorResult to indicate that we + * should just rethrow whatever is on the JSContext (which might be + * nothing if an uncatchable exception was thrown). + */ + ERROR(NS_ERROR_DOM_EXCEPTION_ON_JSCONTEXT, FAILURE(1018)), + + ERROR(NS_ERROR_DOM_MALFORMED_URI, FAILURE(1019)), + ERROR(NS_ERROR_DOM_INVALID_HEADER_NAME, FAILURE(1020)), + + ERROR(NS_ERROR_DOM_INVALID_STATE_XHR_HAS_INVALID_CONTEXT, FAILURE(1021)), + ERROR(NS_ERROR_DOM_INVALID_STATE_XHR_MUST_BE_OPENED, FAILURE(1022)), + ERROR(NS_ERROR_DOM_INVALID_STATE_XHR_MUST_NOT_BE_SENDING, FAILURE(1023)), + ERROR(NS_ERROR_DOM_INVALID_STATE_XHR_MUST_NOT_BE_LOADING_OR_DONE, FAILURE(1024)), + ERROR(NS_ERROR_DOM_INVALID_STATE_XHR_HAS_WRONG_RESPONSETYPE_FOR_RESPONSEXML, FAILURE(1025)), + ERROR(NS_ERROR_DOM_INVALID_STATE_XHR_HAS_WRONG_RESPONSETYPE_FOR_RESPONSETEXT, FAILURE(1026)), + ERROR(NS_ERROR_DOM_INVALID_STATE_XHR_CHUNKED_RESPONSETYPES_UNSUPPORTED_FOR_SYNC, FAILURE(1027)), + ERROR(NS_ERROR_DOM_INVALID_ACCESS_XHR_TIMEOUT_AND_RESPONSETYPE_UNSUPPORTED_FOR_SYNC, FAILURE(1028)), + + /* May be used to indicate when e.g. setting a property value didn't + * actually change the value, like for obj.foo = "bar"; obj.foo = "bar"; + * the second assignment throws NS_SUCCESS_DOM_NO_OPERATION. + */ + ERROR(NS_SUCCESS_DOM_NO_OPERATION, SUCCESS(1)), + + /* + * A success code that indicates that evaluating a string of JS went + * just fine except it threw an exception. Only for legacy use by + * nsJSUtils. + */ + ERROR(NS_SUCCESS_DOM_SCRIPT_EVALUATION_THREW, SUCCESS(2)), + + /* + * A success code that indicates that evaluating a string of JS went + * just fine except it was killed by an uncatchable exception. + * Only for legacy use by nsJSUtils. + */ + ERROR(NS_SUCCESS_DOM_SCRIPT_EVALUATION_THREW_UNCATCHABLE, SUCCESS(3)), +#undef MODULE + + + /* ======================================================================= */ + /* 15: NS_ERROR_MODULE_IMGLIB */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_IMGLIB + ERROR(NS_IMAGELIB_SUCCESS_LOAD_FINISHED, SUCCESS(0)), + ERROR(NS_IMAGELIB_CHANGING_OWNER, SUCCESS(1)), + + ERROR(NS_IMAGELIB_ERROR_FAILURE, FAILURE(5)), + ERROR(NS_IMAGELIB_ERROR_NO_DECODER, FAILURE(6)), + ERROR(NS_IMAGELIB_ERROR_NOT_FINISHED, FAILURE(7)), + ERROR(NS_IMAGELIB_ERROR_NO_ENCODER, FAILURE(9)), +#undef MODULE + + + /* ======================================================================= */ + /* 17: NS_ERROR_MODULE_EDITOR */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_EDITOR + ERROR(NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND, SUCCESS(1)), + ERROR(NS_SUCCESS_EDITOR_FOUND_TARGET, SUCCESS(2)), +#undef MODULE + + + /* ======================================================================= */ + /* 18: NS_ERROR_MODULE_XPCONNECT */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_XPCONNECT + ERROR(NS_ERROR_XPC_NOT_ENOUGH_ARGS, FAILURE(1)), + ERROR(NS_ERROR_XPC_NEED_OUT_OBJECT, FAILURE(2)), + ERROR(NS_ERROR_XPC_CANT_SET_OUT_VAL, FAILURE(3)), + ERROR(NS_ERROR_XPC_NATIVE_RETURNED_FAILURE, FAILURE(4)), + ERROR(NS_ERROR_XPC_CANT_GET_INTERFACE_INFO, FAILURE(5)), + ERROR(NS_ERROR_XPC_CANT_GET_PARAM_IFACE_INFO, FAILURE(6)), + ERROR(NS_ERROR_XPC_CANT_GET_METHOD_INFO, FAILURE(7)), + ERROR(NS_ERROR_XPC_UNEXPECTED, FAILURE(8)), + ERROR(NS_ERROR_XPC_BAD_CONVERT_JS, FAILURE(9)), + ERROR(NS_ERROR_XPC_BAD_CONVERT_NATIVE, FAILURE(10)), + ERROR(NS_ERROR_XPC_BAD_CONVERT_JS_NULL_REF, FAILURE(11)), + ERROR(NS_ERROR_XPC_BAD_OP_ON_WN_PROTO, FAILURE(12)), + ERROR(NS_ERROR_XPC_CANT_CONVERT_WN_TO_FUN, FAILURE(13)), + ERROR(NS_ERROR_XPC_CANT_DEFINE_PROP_ON_WN, FAILURE(14)), + ERROR(NS_ERROR_XPC_CANT_WATCH_WN_STATIC, FAILURE(15)), + ERROR(NS_ERROR_XPC_CANT_EXPORT_WN_STATIC, FAILURE(16)), + ERROR(NS_ERROR_XPC_SCRIPTABLE_CALL_FAILED, FAILURE(17)), + ERROR(NS_ERROR_XPC_SCRIPTABLE_CTOR_FAILED, FAILURE(18)), + ERROR(NS_ERROR_XPC_CANT_CALL_WO_SCRIPTABLE, FAILURE(19)), + ERROR(NS_ERROR_XPC_CANT_CTOR_WO_SCRIPTABLE, FAILURE(20)), + ERROR(NS_ERROR_XPC_CI_RETURNED_FAILURE, FAILURE(21)), + ERROR(NS_ERROR_XPC_GS_RETURNED_FAILURE, FAILURE(22)), + ERROR(NS_ERROR_XPC_BAD_CID, FAILURE(23)), + ERROR(NS_ERROR_XPC_BAD_IID, FAILURE(24)), + ERROR(NS_ERROR_XPC_CANT_CREATE_WN, FAILURE(25)), + ERROR(NS_ERROR_XPC_JS_THREW_EXCEPTION, FAILURE(26)), + ERROR(NS_ERROR_XPC_JS_THREW_NATIVE_OBJECT, FAILURE(27)), + ERROR(NS_ERROR_XPC_JS_THREW_JS_OBJECT, FAILURE(28)), + ERROR(NS_ERROR_XPC_JS_THREW_NULL, FAILURE(29)), + ERROR(NS_ERROR_XPC_JS_THREW_STRING, FAILURE(30)), + ERROR(NS_ERROR_XPC_JS_THREW_NUMBER, FAILURE(31)), + ERROR(NS_ERROR_XPC_JAVASCRIPT_ERROR, FAILURE(32)), + ERROR(NS_ERROR_XPC_JAVASCRIPT_ERROR_WITH_DETAILS, FAILURE(33)), + ERROR(NS_ERROR_XPC_CANT_CONVERT_PRIMITIVE_TO_ARRAY, FAILURE(34)), + ERROR(NS_ERROR_XPC_CANT_CONVERT_OBJECT_TO_ARRAY, FAILURE(35)), + ERROR(NS_ERROR_XPC_NOT_ENOUGH_ELEMENTS_IN_ARRAY, FAILURE(36)), + ERROR(NS_ERROR_XPC_CANT_GET_ARRAY_INFO, FAILURE(37)), + ERROR(NS_ERROR_XPC_NOT_ENOUGH_CHARS_IN_STRING, FAILURE(38)), + ERROR(NS_ERROR_XPC_SECURITY_MANAGER_VETO, FAILURE(39)), + ERROR(NS_ERROR_XPC_INTERFACE_NOT_SCRIPTABLE, FAILURE(40)), + ERROR(NS_ERROR_XPC_INTERFACE_NOT_FROM_NSISUPPORTS, FAILURE(41)), + ERROR(NS_ERROR_XPC_CANT_GET_JSOBJECT_OF_DOM_OBJECT, FAILURE(42)), + ERROR(NS_ERROR_XPC_CANT_SET_READ_ONLY_CONSTANT, FAILURE(43)), + ERROR(NS_ERROR_XPC_CANT_SET_READ_ONLY_ATTRIBUTE, FAILURE(44)), + ERROR(NS_ERROR_XPC_CANT_SET_READ_ONLY_METHOD, FAILURE(45)), + ERROR(NS_ERROR_XPC_CANT_ADD_PROP_TO_WRAPPED_NATIVE, FAILURE(46)), + ERROR(NS_ERROR_XPC_CALL_TO_SCRIPTABLE_FAILED, FAILURE(47)), + ERROR(NS_ERROR_XPC_JSOBJECT_HAS_NO_FUNCTION_NAMED, FAILURE(48)), + ERROR(NS_ERROR_XPC_BAD_ID_STRING, FAILURE(49)), + ERROR(NS_ERROR_XPC_BAD_INITIALIZER_NAME, FAILURE(50)), + ERROR(NS_ERROR_XPC_HAS_BEEN_SHUTDOWN, FAILURE(51)), + ERROR(NS_ERROR_XPC_CANT_MODIFY_PROP_ON_WN, FAILURE(52)), + ERROR(NS_ERROR_XPC_BAD_CONVERT_JS_ZERO_ISNOT_NULL, FAILURE(53)), + ERROR(NS_ERROR_XPC_CANT_PASS_CPOW_TO_NATIVE, FAILURE(54)), + /* any new errors here should have an associated entry added in xpc.msg */ +#undef MODULE + + + /* ======================================================================= */ + /* 19: NS_ERROR_MODULE_PROFILE */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_PROFILE + ERROR(NS_ERROR_LAUNCHED_CHILD_PROCESS, FAILURE(200)), +#undef MODULE + + + /* ======================================================================= */ + /* 21: NS_ERROR_MODULE_SECURITY */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_SECURITY + /* Error code for CSP */ + ERROR(NS_ERROR_CSP_FORM_ACTION_VIOLATION, FAILURE(98)), + ERROR(NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION, FAILURE(99)), + + /* Error code for Sub-Resource Integrity */ + ERROR(NS_ERROR_SRI_CORRUPT, FAILURE(200)), + ERROR(NS_ERROR_SRI_DISABLED, FAILURE(201)), + ERROR(NS_ERROR_SRI_NOT_ELIGIBLE, FAILURE(202)), + ERROR(NS_ERROR_SRI_UNEXPECTED_HASH_TYPE, FAILURE(203)), + ERROR(NS_ERROR_SRI_IMPORT, FAILURE(204)), + + /* CMS specific nsresult error codes. Note: the numbers used here correspond + * to the values in nsICMSMessageErrors.idl. */ + ERROR(NS_ERROR_CMS_VERIFY_NOT_SIGNED, FAILURE(1024)), + ERROR(NS_ERROR_CMS_VERIFY_NO_CONTENT_INFO, FAILURE(1025)), + ERROR(NS_ERROR_CMS_VERIFY_BAD_DIGEST, FAILURE(1026)), + ERROR(NS_ERROR_CMS_VERIFY_NOCERT, FAILURE(1028)), + ERROR(NS_ERROR_CMS_VERIFY_UNTRUSTED, FAILURE(1029)), + ERROR(NS_ERROR_CMS_VERIFY_ERROR_UNVERIFIED, FAILURE(1031)), + ERROR(NS_ERROR_CMS_VERIFY_ERROR_PROCESSING, FAILURE(1032)), + ERROR(NS_ERROR_CMS_VERIFY_BAD_SIGNATURE, FAILURE(1033)), + ERROR(NS_ERROR_CMS_VERIFY_DIGEST_MISMATCH, FAILURE(1034)), + ERROR(NS_ERROR_CMS_VERIFY_UNKNOWN_ALGO, FAILURE(1035)), + ERROR(NS_ERROR_CMS_VERIFY_UNSUPPORTED_ALGO, FAILURE(1036)), + ERROR(NS_ERROR_CMS_VERIFY_MALFORMED_SIGNATURE, FAILURE(1037)), + ERROR(NS_ERROR_CMS_VERIFY_HEADER_MISMATCH, FAILURE(1038)), + ERROR(NS_ERROR_CMS_VERIFY_NOT_YET_ATTEMPTED, FAILURE(1039)), + ERROR(NS_ERROR_CMS_VERIFY_CERT_WITHOUT_ADDRESS, FAILURE(1040)), + ERROR(NS_ERROR_CMS_ENCRYPT_NO_BULK_ALG, FAILURE(1056)), + ERROR(NS_ERROR_CMS_ENCRYPT_INCOMPLETE, FAILURE(1057)), +#undef MODULE + + + /* ======================================================================= */ + /* 22: NS_ERROR_MODULE_DOM_XPATH */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_DOM_XPATH + /* DOM error codes from http://www.w3.org/TR/DOM-Level-3-XPath/ */ + ERROR(NS_ERROR_DOM_INVALID_EXPRESSION_ERR, FAILURE(51)), + ERROR(NS_ERROR_DOM_TYPE_ERR, FAILURE(52)), +#undef MODULE + + + /* ======================================================================= */ + /* 24: NS_ERROR_MODULE_URILOADER */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_URILOADER + ERROR(NS_ERROR_WONT_HANDLE_CONTENT, FAILURE(1)), + /* The load has been cancelled because it was found on a malware or phishing + * blacklist. */ + ERROR(NS_ERROR_MALWARE_URI, FAILURE(30)), + ERROR(NS_ERROR_PHISHING_URI, FAILURE(31)), + ERROR(NS_ERROR_TRACKING_URI, FAILURE(34)), + ERROR(NS_ERROR_UNWANTED_URI, FAILURE(35)), + ERROR(NS_ERROR_BLOCKED_URI, FAILURE(37)), + /* Used when "Save Link As..." doesn't see the headers quickly enough to + * choose a filename. See nsContextMenu.js. */ + ERROR(NS_ERROR_SAVE_LINK_AS_TIMEOUT, FAILURE(32)), + /* Used when the data from a channel has already been parsed and cached so it + * doesn't need to be reparsed from the original source. */ + ERROR(NS_ERROR_PARSED_DATA_CACHED, FAILURE(33)), + + /* This success code indicates that a refresh header was found and + * successfully setup. */ + ERROR(NS_REFRESHURI_HEADER_FOUND, SUCCESS(2)), +#undef MODULE + + + /* ======================================================================= */ + /* 25: NS_ERROR_MODULE_CONTENT */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_CONTENT + /* Error codes for image loading */ + ERROR(NS_ERROR_IMAGE_SRC_CHANGED, FAILURE(4)), + ERROR(NS_ERROR_IMAGE_BLOCKED, FAILURE(5)), + /* Error codes for content policy blocking */ + ERROR(NS_ERROR_CONTENT_BLOCKED, FAILURE(6)), + ERROR(NS_ERROR_CONTENT_BLOCKED_SHOW_ALT, FAILURE(7)), + /* Success variations of content policy blocking */ + ERROR(NS_PROPTABLE_PROP_NOT_THERE, FAILURE(10)), + /* Error code for XBL */ + ERROR(NS_ERROR_XBL_BLOCKED, FAILURE(15)), + /* Error code for when the content process crashed */ + ERROR(NS_ERROR_CONTENT_CRASHED, FAILURE(16)), + + /* XXX this is not really used */ + ERROR(NS_HTML_STYLE_PROPERTY_NOT_THERE, SUCCESS(2)), + ERROR(NS_CONTENT_BLOCKED, SUCCESS(8)), + ERROR(NS_CONTENT_BLOCKED_SHOW_ALT, SUCCESS(9)), + ERROR(NS_PROPTABLE_PROP_OVERWRITTEN, SUCCESS(11)), + /* Error codes for FindBroadcaster in XULDocument.cpp */ + ERROR(NS_FINDBROADCASTER_NOT_FOUND, SUCCESS(12)), + ERROR(NS_FINDBROADCASTER_FOUND, SUCCESS(13)), + ERROR(NS_FINDBROADCASTER_AWAIT_OVERLAYS, SUCCESS(14)), +#undef MODULE + + + /* ======================================================================= */ + /* 27: NS_ERROR_MODULE_XSLT */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_XSLT + ERROR(NS_ERROR_XPATH_INVALID_ARG, NS_ERROR_INVALID_ARG), + + ERROR(NS_ERROR_XSLT_PARSE_FAILURE, FAILURE(1)), + ERROR(NS_ERROR_XPATH_PARSE_FAILURE, FAILURE(2)), + ERROR(NS_ERROR_XSLT_ALREADY_SET, FAILURE(3)), + ERROR(NS_ERROR_XSLT_EXECUTION_FAILURE, FAILURE(4)), + ERROR(NS_ERROR_XPATH_UNKNOWN_FUNCTION, FAILURE(5)), + ERROR(NS_ERROR_XSLT_BAD_RECURSION, FAILURE(6)), + ERROR(NS_ERROR_XSLT_BAD_VALUE, FAILURE(7)), + ERROR(NS_ERROR_XSLT_NODESET_EXPECTED, FAILURE(8)), + ERROR(NS_ERROR_XSLT_ABORTED, FAILURE(9)), + ERROR(NS_ERROR_XSLT_NETWORK_ERROR, FAILURE(10)), + ERROR(NS_ERROR_XSLT_WRONG_MIME_TYPE, FAILURE(11)), + ERROR(NS_ERROR_XSLT_LOAD_RECURSION, FAILURE(12)), + ERROR(NS_ERROR_XPATH_BAD_ARGUMENT_COUNT, FAILURE(13)), + ERROR(NS_ERROR_XPATH_BAD_EXTENSION_FUNCTION, FAILURE(14)), + ERROR(NS_ERROR_XPATH_PAREN_EXPECTED, FAILURE(15)), + ERROR(NS_ERROR_XPATH_INVALID_AXIS, FAILURE(16)), + ERROR(NS_ERROR_XPATH_NO_NODE_TYPE_TEST, FAILURE(17)), + ERROR(NS_ERROR_XPATH_BRACKET_EXPECTED, FAILURE(18)), + ERROR(NS_ERROR_XPATH_INVALID_VAR_NAME, FAILURE(19)), + ERROR(NS_ERROR_XPATH_UNEXPECTED_END, FAILURE(20)), + ERROR(NS_ERROR_XPATH_OPERATOR_EXPECTED, FAILURE(21)), + ERROR(NS_ERROR_XPATH_UNCLOSED_LITERAL, FAILURE(22)), + ERROR(NS_ERROR_XPATH_BAD_COLON, FAILURE(23)), + ERROR(NS_ERROR_XPATH_BAD_BANG, FAILURE(24)), + ERROR(NS_ERROR_XPATH_ILLEGAL_CHAR, FAILURE(25)), + ERROR(NS_ERROR_XPATH_BINARY_EXPECTED, FAILURE(26)), + ERROR(NS_ERROR_XSLT_LOAD_BLOCKED_ERROR, FAILURE(27)), + ERROR(NS_ERROR_XPATH_INVALID_EXPRESSION_EVALUATED, FAILURE(28)), + ERROR(NS_ERROR_XPATH_UNBALANCED_CURLY_BRACE, FAILURE(29)), + ERROR(NS_ERROR_XSLT_BAD_NODE_NAME, FAILURE(30)), + ERROR(NS_ERROR_XSLT_VAR_ALREADY_SET, FAILURE(31)), + ERROR(NS_ERROR_XSLT_CALL_TO_KEY_NOT_ALLOWED, FAILURE(32)), + + ERROR(NS_XSLT_GET_NEW_HANDLER, SUCCESS(1)), +#undef MODULE + + + /* ======================================================================= */ + /* 28: NS_ERROR_MODULE_IPC */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_IPC + // Initial creation of a Transport object failed internally for unknown reasons. + ERROR(NS_ERROR_TRANSPORT_INIT, FAILURE(1)), + // Generic error related to duplicating handle failures. + ERROR(NS_ERROR_DUPLICATE_HANDLE, FAILURE(2)), + // Bridging: failure trying to open the connection to the parent + ERROR(NS_ERROR_BRIDGE_OPEN_PARENT, FAILURE(3)), + // Bridging: failure trying to open the connection to the child + ERROR(NS_ERROR_BRIDGE_OPEN_CHILD, FAILURE(4)), +#undef MODULE + + /* ======================================================================= */ + /* 29: NS_ERROR_MODULE_SVG */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_SVG + /* SVG DOM error codes from http://www.w3.org/TR/SVG11/svgdom.html */ + ERROR(NS_ERROR_DOM_SVG_WRONG_TYPE_ERR, FAILURE(0)), + /* Yes, the spec says "INVERTABLE", not "INVERTIBLE" */ + ERROR(NS_ERROR_DOM_SVG_MATRIX_NOT_INVERTABLE, FAILURE(2)), +#undef MODULE + + + /* ======================================================================= */ + /* 30: NS_ERROR_MODULE_STORAGE */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_STORAGE + /* To add additional errors to Storage, please append entries to the bottom + * of the list in the following format: + * NS_ERROR_STORAGE_YOUR_ERR, FAILURE(n) + * where n is the next unique positive integer. You must also add an entry + * to js/xpconnect/src/xpc.msg under the code block beginning with the + * comment 'storage related codes (from mozStorage.h)', in the following + * format: 'XPC_MSG_DEF(NS_ERROR_STORAGE_YOUR_ERR, "brief description of your + * error")' */ + ERROR(NS_ERROR_STORAGE_BUSY, FAILURE(1)), + ERROR(NS_ERROR_STORAGE_IOERR, FAILURE(2)), + ERROR(NS_ERROR_STORAGE_CONSTRAINT, FAILURE(3)), +#undef MODULE + + + /* ======================================================================= */ + /* 32: NS_ERROR_MODULE_DOM_FILE */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_DOM_FILE + ERROR(NS_ERROR_DOM_FILE_NOT_FOUND_ERR, FAILURE(0)), + ERROR(NS_ERROR_DOM_FILE_NOT_READABLE_ERR, FAILURE(1)), + ERROR(NS_ERROR_DOM_FILE_ABORT_ERR, FAILURE(2)), +#undef MODULE + + + /* ======================================================================= */ + /* 33: NS_ERROR_MODULE_DOM_INDEXEDDB */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_DOM_INDEXEDDB + /* IndexedDB error codes http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html */ + ERROR(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR, FAILURE(1)), + ERROR(NS_ERROR_DOM_INDEXEDDB_NOT_FOUND_ERR, FAILURE(3)), + ERROR(NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR, FAILURE(4)), + ERROR(NS_ERROR_DOM_INDEXEDDB_DATA_ERR, FAILURE(5)), + ERROR(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR, FAILURE(6)), + ERROR(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR, FAILURE(7)), + ERROR(NS_ERROR_DOM_INDEXEDDB_ABORT_ERR, FAILURE(8)), + ERROR(NS_ERROR_DOM_INDEXEDDB_READ_ONLY_ERR, FAILURE(9)), + ERROR(NS_ERROR_DOM_INDEXEDDB_TIMEOUT_ERR, FAILURE(10)), + ERROR(NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR, FAILURE(11)), + ERROR(NS_ERROR_DOM_INDEXEDDB_VERSION_ERR, FAILURE(12)), + ERROR(NS_ERROR_DOM_INDEXEDDB_RECOVERABLE_ERR, FAILURE(1001)), +#undef MODULE + + + /* ======================================================================= */ + /* 34: NS_ERROR_MODULE_DOM_FILEHANDLE */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_DOM_FILEHANDLE + ERROR(NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR, FAILURE(1)), + ERROR(NS_ERROR_DOM_FILEHANDLE_NOT_ALLOWED_ERR, FAILURE(2)), + ERROR(NS_ERROR_DOM_FILEHANDLE_INACTIVE_ERR, FAILURE(3)), + ERROR(NS_ERROR_DOM_FILEHANDLE_ABORT_ERR, FAILURE(4)), + ERROR(NS_ERROR_DOM_FILEHANDLE_READ_ONLY_ERR, FAILURE(5)), + ERROR(NS_ERROR_DOM_FILEHANDLE_QUOTA_ERR, FAILURE(6)), +#undef MODULE + + /* ======================================================================= */ + /* 35: NS_ERROR_MODULE_SIGNED_JAR */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_SIGNED_JAR + ERROR(NS_ERROR_SIGNED_JAR_NOT_SIGNED, FAILURE(1)), + ERROR(NS_ERROR_SIGNED_JAR_MODIFIED_ENTRY, FAILURE(2)), + ERROR(NS_ERROR_SIGNED_JAR_UNSIGNED_ENTRY, FAILURE(3)), + ERROR(NS_ERROR_SIGNED_JAR_ENTRY_MISSING, FAILURE(4)), + ERROR(NS_ERROR_SIGNED_JAR_WRONG_SIGNATURE, FAILURE(5)), + ERROR(NS_ERROR_SIGNED_JAR_ENTRY_TOO_LARGE, FAILURE(6)), + ERROR(NS_ERROR_SIGNED_JAR_ENTRY_INVALID, FAILURE(7)), + ERROR(NS_ERROR_SIGNED_JAR_MANIFEST_INVALID, FAILURE(8)), +#undef MODULE + + /* ======================================================================= */ + /* 36: NS_ERROR_MODULE_DOM_FILESYSTEM */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_DOM_FILESYSTEM + ERROR(NS_ERROR_DOM_FILESYSTEM_INVALID_PATH_ERR, FAILURE(1)), + ERROR(NS_ERROR_DOM_FILESYSTEM_INVALID_MODIFICATION_ERR, FAILURE(2)), + ERROR(NS_ERROR_DOM_FILESYSTEM_NO_MODIFICATION_ALLOWED_ERR, FAILURE(3)), + ERROR(NS_ERROR_DOM_FILESYSTEM_PATH_EXISTS_ERR, FAILURE(4)), + ERROR(NS_ERROR_DOM_FILESYSTEM_TYPE_MISMATCH_ERR, FAILURE(5)), + ERROR(NS_ERROR_DOM_FILESYSTEM_UNKNOWN_ERR, FAILURE(6)), +#undef MODULE + + /* ======================================================================= */ + /* 38: NS_ERROR_MODULE_SIGNED_APP */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_SIGNED_APP + ERROR(NS_ERROR_SIGNED_APP_MANIFEST_INVALID, FAILURE(1)), +#undef MODULE + + /* ======================================================================= */ + /* 39: NS_ERROR_MODULE_DOM_ANIM */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_DOM_ANIM + ERROR(NS_ERROR_DOM_ANIM_MISSING_PROPS_ERR, FAILURE(1)), +#undef MODULE + + /* ======================================================================= */ + /* 40: NS_ERROR_MODULE_DOM_PUSH */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_DOM_PUSH + ERROR(NS_ERROR_DOM_PUSH_INVALID_REGISTRATION_ERR, FAILURE(1)), + ERROR(NS_ERROR_DOM_PUSH_DENIED_ERR, FAILURE(2)), + ERROR(NS_ERROR_DOM_PUSH_ABORT_ERR, FAILURE(3)), + ERROR(NS_ERROR_DOM_PUSH_SERVICE_UNREACHABLE, FAILURE(4)), + ERROR(NS_ERROR_DOM_PUSH_INVALID_KEY_ERR, FAILURE(5)), + ERROR(NS_ERROR_DOM_PUSH_MISMATCHED_KEY_ERR, FAILURE(6)), +#undef MODULE + + /* ======================================================================= */ + /* 41: NS_ERROR_MODULE_DOM_MEDIA */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_DOM_MEDIA + /* HTMLMediaElement API errors from https://html.spec.whatwg.org/multipage/embedded-content.html#media-elements */ + ERROR(NS_ERROR_DOM_MEDIA_ABORT_ERR, FAILURE(1)), + ERROR(NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR, FAILURE(2)), + ERROR(NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR, FAILURE(3)), + + /* HTMLMediaElement internal decoding error */ + ERROR(NS_ERROR_DOM_MEDIA_DECODE_ERR, FAILURE(4)), + ERROR(NS_ERROR_DOM_MEDIA_FATAL_ERR, FAILURE(5)), + ERROR(NS_ERROR_DOM_MEDIA_METADATA_ERR, FAILURE(6)), + ERROR(NS_ERROR_DOM_MEDIA_OVERFLOW_ERR, FAILURE(7)), + ERROR(NS_ERROR_DOM_MEDIA_END_OF_STREAM, FAILURE(8)), + ERROR(NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA, FAILURE(9)), + ERROR(NS_ERROR_DOM_MEDIA_CANCELED, FAILURE(10)), + ERROR(NS_ERROR_DOM_MEDIA_MEDIASINK_ERR, FAILURE(11)), + ERROR(NS_ERROR_DOM_MEDIA_DEMUXER_ERR, FAILURE(12)), + ERROR(NS_ERROR_DOM_MEDIA_CDM_ERR, FAILURE(13)), + ERROR(NS_ERROR_DOM_MEDIA_NEED_NEW_DECODER, FAILURE(14)), + + /* Internal platform-related errors */ + ERROR(NS_ERROR_DOM_MEDIA_CUBEB_INITIALIZATION_ERR, FAILURE(101)), +#undef MODULE + + /* ======================================================================= */ + /* 51: NS_ERROR_MODULE_GENERAL */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_GENERAL + /* Error code used internally by the incremental downloader to cancel the + * network channel when the download is already complete. */ + ERROR(NS_ERROR_DOWNLOAD_COMPLETE, FAILURE(1)), + /* Error code used internally by the incremental downloader to cancel the + * network channel when the response to a range request is 200 instead of + * 206. */ + ERROR(NS_ERROR_DOWNLOAD_NOT_PARTIAL, FAILURE(2)), + ERROR(NS_ERROR_UNORM_MOREOUTPUT, FAILURE(33)), + + ERROR(NS_ERROR_DOCSHELL_REQUEST_REJECTED, FAILURE(1001)), + /* This is needed for displaying an error message when navigation is + * attempted on a document when printing The value arbitrary as long as it + * doesn't conflict with any of the other values in the errors in + * DisplayLoadError */ + ERROR(NS_ERROR_DOCUMENT_IS_PRINTMODE, FAILURE(2001)), + + ERROR(NS_SUCCESS_DONT_FIXUP, SUCCESS(1)), + /* This success code may be returned by nsIAppStartup::Run to indicate that + * the application should be restarted. This condition corresponds to the + * case in which nsIAppStartup::Quit was called with the eRestart flag. */ + ERROR(NS_SUCCESS_RESTART_APP, SUCCESS(1)), + ERROR(NS_SUCCESS_RESTART_APP_NOT_SAME_PROFILE, SUCCESS(3)), + ERROR(NS_SUCCESS_UNORM_NOTFOUND, SUCCESS(17)), + + + /* a11y */ + /* raised when current pivot's position is needed but it's not in the tree */ + ERROR(NS_ERROR_NOT_IN_TREE, FAILURE(38)), + + /* see nsTextEquivUtils */ + ERROR(NS_OK_NO_NAME_CLAUSE_HANDLED, SUCCESS(34)) +#undef MODULE diff --git a/xpcom/base/ErrorNames.cpp b/xpcom/base/ErrorNames.cpp new file mode 100644 index 000000000..165a1a0fc --- /dev/null +++ b/xpcom/base/ErrorNames.cpp @@ -0,0 +1,84 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/ArrayUtils.h" +#include "mozilla/ErrorNames.h" +#include "nsString.h" +#include "prerror.h" + +namespace { + +struct ErrorEntry +{ + nsresult value; + const char * name; +}; + +#undef ERROR +#define ERROR(key, val) {key, #key} + +const ErrorEntry errors[] = { + #include "ErrorList.h" +}; + +#undef ERROR + +} // unnamed namespace + +namespace mozilla { + +void +GetErrorName(nsresult rv, nsACString& name) +{ + for (size_t i = 0; i < ArrayLength(errors); ++i) { + if (errors[i].value == rv) { + name.AssignASCII(errors[i].name); + return; + } + } + + bool isSecurityError = NS_ERROR_GET_MODULE(rv) == NS_ERROR_MODULE_SECURITY; + + // NS_ERROR_MODULE_SECURITY is the only module that is "allowed" to + // synthesize nsresult error codes that are not listed in ErrorList.h. (The + // NS_ERROR_MODULE_SECURITY error codes are synthesized from NSPR error + // codes.) + MOZ_ASSERT(isSecurityError); + + name.AssignASCII(NS_SUCCEEDED(rv) ? "NS_ERROR_GENERATE_SUCCESS(" + : "NS_ERROR_GENERATE_FAILURE("); + + if (isSecurityError) { + name.AppendASCII("NS_ERROR_MODULE_SECURITY"); + } else { + // This should never happen given the assertion above, so we don't bother + // trying to print a symbolic name for the module here. + name.AppendInt(NS_ERROR_GET_MODULE(rv)); + } + + name.AppendASCII(", "); + + const char * nsprName = nullptr; + if (isSecurityError) { + // Invert the logic from NSSErrorsService::GetXPCOMFromNSSError + PRErrorCode nsprCode + = -1 * static_cast<PRErrorCode>(NS_ERROR_GET_CODE(rv)); + nsprName = PR_ErrorToName(nsprCode); + + // All NSPR error codes defined by NSPR or NSS should have a name mapping. + MOZ_ASSERT(nsprName); + } + + if (nsprName) { + name.AppendASCII(nsprName); + } else { + name.AppendInt(NS_ERROR_GET_CODE(rv)); + } + + name.AppendASCII(")"); +} + +} // namespace mozilla diff --git a/xpcom/base/ErrorNames.h b/xpcom/base/ErrorNames.h new file mode 100644 index 000000000..9fdba7ace --- /dev/null +++ b/xpcom/base/ErrorNames.h @@ -0,0 +1,25 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_ErrorNames_h +#define mozilla_ErrorNames_h + +#include "nsError.h" + +class nsACString; + +namespace mozilla { + +// Maps the given nsresult to its symbolic name. For example, +// GetErrorName(NS_OK, name) will result in name == "NS_OK". +// When the symbolic name is unknown, name will be of the form +// "NS_ERROR_GENERATE_SUCCESS(<module>, <code>)" or +// "NS_ERROR_GENERATE_FAILURE(<module>, <code>)". +void GetErrorName(nsresult rv, nsACString& name); + +} // namespace mozilla + +#endif // mozilla_ErrorNames_h diff --git a/xpcom/base/HoldDropJSObjects.cpp b/xpcom/base/HoldDropJSObjects.cpp new file mode 100644 index 000000000..eeecc7121 --- /dev/null +++ b/xpcom/base/HoldDropJSObjects.cpp @@ -0,0 +1,68 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/HoldDropJSObjects.h" + +#include "mozilla/Assertions.h" +#include "mozilla/CycleCollectedJSContext.h" + +namespace mozilla { +namespace cyclecollector { + +void +HoldJSObjectsImpl(void* aHolder, nsScriptObjectTracer* aTracer) +{ + CycleCollectedJSContext* cx = CycleCollectedJSContext::Get(); + MOZ_ASSERT(cx, "Should have a CycleCollectedJSContext by now"); + cx->AddJSHolder(aHolder, aTracer); +} + +void +HoldJSObjectsImpl(nsISupports* aHolder) +{ + nsXPCOMCycleCollectionParticipant* participant = nullptr; + CallQueryInterface(aHolder, &participant); + MOZ_ASSERT(participant, "Failed to QI to nsXPCOMCycleCollectionParticipant!"); + MOZ_ASSERT(participant->CheckForRightISupports(aHolder), + "The result of QIing a JS holder should be the same as ToSupports"); + + HoldJSObjectsImpl(aHolder, participant); +} + +void +DropJSObjectsImpl(void* aHolder) +{ + CycleCollectedJSContext* cx = CycleCollectedJSContext::Get(); + MOZ_ASSERT(cx, "Should have a CycleCollectedJSContext by now"); + cx->RemoveJSHolder(aHolder); +} + +void +DropJSObjectsImpl(nsISupports* aHolder) +{ +#ifdef DEBUG + nsXPCOMCycleCollectionParticipant* participant = nullptr; + CallQueryInterface(aHolder, &participant); + MOZ_ASSERT(participant, "Failed to QI to nsXPCOMCycleCollectionParticipant!"); + MOZ_ASSERT(participant->CheckForRightISupports(aHolder), + "The result of QIing a JS holder should be the same as ToSupports"); +#endif + DropJSObjectsImpl(static_cast<void*>(aHolder)); +} + +} // namespace cyclecollector + +#ifdef DEBUG +bool +IsJSHolder(void* aHolder) +{ + CycleCollectedJSContext* cx = CycleCollectedJSContext::Get(); + MOZ_ASSERT(cx, "Should have a CycleCollectedJSContext by now"); + return cx->IsJSHolder(aHolder); +} +#endif + +} // namespace mozilla diff --git a/xpcom/base/HoldDropJSObjects.h b/xpcom/base/HoldDropJSObjects.h new file mode 100644 index 000000000..1a500a94a --- /dev/null +++ b/xpcom/base/HoldDropJSObjects.h @@ -0,0 +1,77 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_HoldDropJSObjects_h +#define mozilla_HoldDropJSObjects_h + +#include "mozilla/TypeTraits.h" +#include "nsCycleCollectionParticipant.h" + +class nsISupports; +class nsScriptObjectTracer; + +// Only HoldJSObjects and DropJSObjects should be called directly. + +namespace mozilla { +namespace cyclecollector { + +void HoldJSObjectsImpl(void* aHolder, nsScriptObjectTracer* aTracer); +void HoldJSObjectsImpl(nsISupports* aHolder); +void DropJSObjectsImpl(void* aHolder); +void DropJSObjectsImpl(nsISupports* aHolder); + +} // namespace cyclecollector + + +template<class T, bool isISupports = IsBaseOf<nsISupports, T>::value> +struct HoldDropJSObjectsHelper +{ + static void Hold(T* aHolder) + { + cyclecollector::HoldJSObjectsImpl(aHolder, + NS_CYCLE_COLLECTION_PARTICIPANT(T)); + } + static void Drop(T* aHolder) + { + cyclecollector::DropJSObjectsImpl(aHolder); + } +}; + +template<class T> +struct HoldDropJSObjectsHelper<T, true> +{ + static void Hold(T* aHolder) + { + cyclecollector::HoldJSObjectsImpl(ToSupports(aHolder)); + } + static void Drop(T* aHolder) + { + cyclecollector::DropJSObjectsImpl(ToSupports(aHolder)); + } +}; + + +template<class T> +void +HoldJSObjects(T* aHolder) +{ + HoldDropJSObjectsHelper<T>::Hold(aHolder); +} + +template<class T> +void +DropJSObjects(T* aHolder) +{ + HoldDropJSObjectsHelper<T>::Drop(aHolder); +} + +#ifdef DEBUG +bool IsJSHolder(void* aHolder); +#endif + +} // namespace mozilla + +#endif // mozilla_HoldDropJSObjects_h diff --git a/xpcom/base/JSObjectHolder.cpp b/xpcom/base/JSObjectHolder.cpp new file mode 100644 index 000000000..5bcc3cabb --- /dev/null +++ b/xpcom/base/JSObjectHolder.cpp @@ -0,0 +1,9 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "JSObjectHolder.h" + +NS_IMPL_ISUPPORTS(mozilla::JSObjectHolder, nsISupports) diff --git a/xpcom/base/JSObjectHolder.h b/xpcom/base/JSObjectHolder.h new file mode 100644 index 000000000..7b83b813d --- /dev/null +++ b/xpcom/base/JSObjectHolder.h @@ -0,0 +1,42 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_JSObjectHolder_h +#define mozilla_JSObjectHolder_h + +#include "js/RootingAPI.h" +#include "nsISupportsImpl.h" + +namespace mozilla { + +// This class is useful when something on one thread needs to keep alive +// a JS Object from another thread. If they are both on the same thread, the +// owning class should instead be made a cycle collected SCRIPT_HOLDER class. +// This object should only be AddRefed and Released on the same thread as +// mJSObject. +// +// Note that this keeps alive the JS object until it goes away, so be sure not to +// create cycles that keep alive the holder. +// +// JSObjectHolder is ISupports to make it usable with NS_ReleaseOnMainThread. +class JSObjectHolder final : public nsISupports +{ +public: + JSObjectHolder(JSContext* aCx, JSObject* aObject) : mJSObject(aCx, aObject) {} + + NS_DECL_ISUPPORTS + + JSObject* GetJSObject() { return mJSObject; } + +private: + ~JSObjectHolder() {} + + JS::PersistentRooted<JSObject*> mJSObject; +}; + +} // namespace mozilla + +#endif // mozilla_JSObjectHolder_h diff --git a/xpcom/base/LinuxUtils.cpp b/xpcom/base/LinuxUtils.cpp new file mode 100644 index 000000000..331c82be9 --- /dev/null +++ b/xpcom/base/LinuxUtils.cpp @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "LinuxUtils.h" + +#if defined(XP_LINUX) + +#include <ctype.h> +#include <stdio.h> + +#include "nsPrintfCString.h" + +namespace mozilla { + +void +LinuxUtils::GetThreadName(pid_t aTid, nsACString& aName) +{ + aName.Truncate(); + if (aTid <= 0) { + return; + } + + const size_t kBuffSize = 16; // 15 chars max + '\n' + char buf[kBuffSize]; + nsPrintfCString path("/proc/%d/comm", aTid); + FILE* fp = fopen(path.get(), "r"); + if (!fp) { + // The fopen could also fail if the thread exited before we got here. + return; + } + + size_t len = fread(buf, 1, kBuffSize, fp); + fclose(fp); + + // No need to strip the '\n', since isspace() includes it. + while (len > 0 && + (isspace(buf[len - 1]) || isdigit(buf[len - 1]) || + buf[len - 1] == '#' || buf[len - 1] == '_')) { + --len; + } + + aName.Assign(buf, len); +} + +} + +#endif // XP_LINUX diff --git a/xpcom/base/LinuxUtils.h b/xpcom/base/LinuxUtils.h new file mode 100644 index 000000000..e82c15e08 --- /dev/null +++ b/xpcom/base/LinuxUtils.h @@ -0,0 +1,34 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_LinuxUtils_h +#define mozilla_LinuxUtils_h + +#if defined(XP_LINUX) + +#include <unistd.h> +#include "nsString.h" + +namespace mozilla { + +class LinuxUtils +{ +public: + // Obtain the name of a thread, omitting any numeric suffix added by a + // thread pool library (as in, e.g., "Binder_2" or "mozStorage #1"). + // The empty string is returned on error. + // + // Note: if this is ever needed on kernels older than 2.6.33 (early 2010), + // it will have to parse /proc/<pid>/status instead, because + // /proc/<pid>/comm didn't exist before then. + static void GetThreadName(pid_t aTid, nsACString& aName); +}; + +} + +#endif // XP_LINUX + +#endif diff --git a/xpcom/base/LogModulePrefWatcher.cpp b/xpcom/base/LogModulePrefWatcher.cpp new file mode 100644 index 000000000..bd04eda98 --- /dev/null +++ b/xpcom/base/LogModulePrefWatcher.cpp @@ -0,0 +1,170 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "LogModulePrefWatcher.h" + +#include "mozilla/Logging.h" +#include "mozilla/Preferences.h" +#include "nsString.h" +#include "nsXULAppAPI.h" +#include "base/process_util.h" + +static const char kLoggingPrefPrefix[] = "logging."; +static const char kLoggingConfigPrefPrefix[] = "logging.config"; +static const int kLoggingConfigPrefixLen = sizeof(kLoggingConfigPrefPrefix) - 1; +static const char kLoggingPrefClearOnStartup[] = "logging.config.clear_on_startup"; +static const char kLoggingPrefLogFile[] = "logging.config.LOG_FILE"; +static const char kLoggingPrefAddTimestamp[] = "logging.config.add_timestamp"; +static const char kLoggingPrefSync[] = "logging.config.sync"; + +namespace mozilla { + +NS_IMPL_ISUPPORTS(LogModulePrefWatcher, nsIObserver) + +/** + * Resets all the preferences in the logging. branch + * This is needed because we may crash while logging, and this would cause us + * to log after restarting as well. + * + * If logging after restart is desired, set the logging.config.clear_on_startup + * pref to false, or use the MOZ_LOG_FILE and MOZ_LOG_MODULES env vars. + */ +void ResetExistingPrefs() +{ + uint32_t count; + char** names; + nsresult rv = Preferences::GetRootBranch()-> + GetChildList(kLoggingPrefPrefix, &count, &names); + if (NS_SUCCEEDED(rv) && count) { + for (size_t i = 0; i < count; i++) { + // Clearing the pref will cause it to reload, thus resetting the log level + Preferences::ClearUser(names[i]); + } + NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count, names); + } +} + +/** + * Loads the log level from the given pref and updates the corresponding + * LogModule. + */ +static void +LoadPrefValue(const char* aName) +{ + LogLevel logLevel = LogLevel::Disabled; + + nsresult rv; + int32_t prefLevel = 0; + nsAutoCString prefValue; + + if (strncmp(aName, kLoggingConfigPrefPrefix, kLoggingConfigPrefixLen) == 0) { + nsAutoCString prefName(aName); + + if (prefName.EqualsLiteral(kLoggingPrefLogFile)) { + rv = Preferences::GetCString(aName, &prefValue); + // The pref was reset. Clear the user file. + if (NS_FAILED(rv) || prefValue.IsEmpty()) { + LogModule::SetLogFile(nullptr); + return; + } + + // If the pref value doesn't have a PID placeholder, append it to the end. + if (!strstr(prefValue.get(), "%PID")) { + prefValue.Append("%PID"); + } + + LogModule::SetLogFile(prefValue.BeginReading()); + } else if (prefName.EqualsLiteral(kLoggingPrefAddTimestamp)) { + bool addTimestamp = Preferences::GetBool(aName, false); + LogModule::SetAddTimestamp(addTimestamp); + } else if (prefName.EqualsLiteral(kLoggingPrefSync)) { + bool sync = Preferences::GetBool(aName, false); + LogModule::SetIsSync(sync); + } + return; + } + + if (Preferences::GetInt(aName, &prefLevel) == NS_OK) { + logLevel = ToLogLevel(prefLevel); + } else if (Preferences::GetCString(aName, &prefValue) == NS_OK) { + if (prefValue.LowerCaseEqualsLiteral("error")) { + logLevel = LogLevel::Error; + } else if (prefValue.LowerCaseEqualsLiteral("warning")) { + logLevel = LogLevel::Warning; + } else if (prefValue.LowerCaseEqualsLiteral("info")) { + logLevel = LogLevel::Info; + } else if (prefValue.LowerCaseEqualsLiteral("debug")) { + logLevel = LogLevel::Debug; + } else if (prefValue.LowerCaseEqualsLiteral("verbose")) { + logLevel = LogLevel::Verbose; + } + } + + const char* moduleName = aName + strlen(kLoggingPrefPrefix); + LogModule::Get(moduleName)->SetLevel(logLevel); +} + +void +LoadExistingPrefs() +{ + nsIPrefBranch* root = Preferences::GetRootBranch(); + if (!root) { + return; + } + + uint32_t count; + char** names; + nsresult rv = root->GetChildList(kLoggingPrefPrefix, &count, &names); + if (NS_SUCCEEDED(rv) && count) { + for (size_t i = 0; i < count; i++) { + LoadPrefValue(names[i]); + } + NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count, names); + } +} + +LogModulePrefWatcher::LogModulePrefWatcher() +{ +} + +void +LogModulePrefWatcher::RegisterPrefWatcher() +{ + RefPtr<LogModulePrefWatcher> prefWatcher = new LogModulePrefWatcher(); + Preferences::AddStrongObserver(prefWatcher, kLoggingPrefPrefix); + + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + if (observerService && XRE_IsParentProcess()) { + observerService->AddObserver(prefWatcher, "browser-delayed-startup-finished", false); + } + + LoadExistingPrefs(); +} + +NS_IMETHODIMP +LogModulePrefWatcher::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) +{ + if (strcmp(NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, aTopic) == 0) { + NS_LossyConvertUTF16toASCII prefName(aData); + LoadPrefValue(prefName.get()); + } else if (strcmp("browser-delayed-startup-finished", aTopic) == 0) { + bool clear = Preferences::GetBool(kLoggingPrefClearOnStartup, true); + if (clear) { + ResetExistingPrefs(); + } + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + if (observerService) { + observerService->RemoveObserver(this, "browser-delayed-startup-finished"); + } + } + + return NS_OK; +} + +} // namespace mozilla diff --git a/xpcom/base/LogModulePrefWatcher.h b/xpcom/base/LogModulePrefWatcher.h new file mode 100644 index 000000000..657e54f01 --- /dev/null +++ b/xpcom/base/LogModulePrefWatcher.h @@ -0,0 +1,42 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef LogModulePrefWatcher_h +#define LogModulePrefWatcher_h + +#include "nsIObserver.h" + +namespace mozilla { + +/** + * Watches for changes to "logging.*" prefs and then updates the appropriate + * LogModule's log level. Both the integer and string versions of the LogLevel + * enum are supported. + * + * For example setting the pref "logging.Foo" to "Verbose" will set the + * LogModule for "Foo" to the LogLevel::Verbose level. Setting "logging.Bar" to + * 4 would set the LogModule for "Bar" to the LogLevel::Debug level. + */ +class LogModulePrefWatcher : public nsIObserver +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + /** + * Starts observing logging pref changes. + */ + static void RegisterPrefWatcher(); + +private: + LogModulePrefWatcher(); + virtual ~LogModulePrefWatcher() + { + } +}; +} // namespace mozilla + +#endif // LogModulePrefWatcher_h diff --git a/xpcom/base/Logging.cpp b/xpcom/base/Logging.cpp new file mode 100644 index 000000000..e87df91e4 --- /dev/null +++ b/xpcom/base/Logging.cpp @@ -0,0 +1,568 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Logging.h" + +#include <algorithm> + +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/FileUtils.h" +#include "mozilla/Mutex.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/Sprintf.h" +#include "mozilla/Atomics.h" +#include "mozilla/UniquePtrExtensions.h" +#include "nsClassHashtable.h" +#include "nsDebug.h" +#include "NSPRLogModulesParser.h" + +#include "prenv.h" +#include "prprf.h" +#ifdef XP_WIN +#include <process.h> +#else +#include <sys/types.h> +#include <unistd.h> +#endif + +// NB: Initial amount determined by auditing the codebase for the total amount +// of unique module names and padding up to the next power of 2. +const uint32_t kInitialModuleCount = 256; +// When rotate option is added to the modules list, this is the hardcoded +// number of files we create and rotate. When there is rotate:40, +// we will keep four files per process, each limited to 10MB. Sum is 40MB, +// the given limit. +const uint32_t kRotateFilesNumber = 4; + +namespace mozilla { + +namespace detail { + +void log_print(const PRLogModuleInfo* aModule, + LogLevel aLevel, + const char* aFmt, ...) +{ + va_list ap; + va_start(ap, aFmt); + char* buff = PR_vsmprintf(aFmt, ap); + PR_LogPrint("%s", buff); + PR_smprintf_free(buff); + va_end(ap); +} + +void log_print(const LogModule* aModule, + LogLevel aLevel, + const char* aFmt, ...) +{ + va_list ap; + va_start(ap, aFmt); + aModule->Printv(aLevel, aFmt, ap); + va_end(ap); +} + +} // detail + +LogLevel +ToLogLevel(int32_t aLevel) +{ + aLevel = std::min(aLevel, static_cast<int32_t>(LogLevel::Verbose)); + aLevel = std::max(aLevel, static_cast<int32_t>(LogLevel::Disabled)); + return static_cast<LogLevel>(aLevel); +} + +const char* +ToLogStr(LogLevel aLevel) { + switch (aLevel) { + case LogLevel::Error: + return "E"; + case LogLevel::Warning: + return "W"; + case LogLevel::Info: + return "I"; + case LogLevel::Debug: + return "D"; + case LogLevel::Verbose: + return "V"; + case LogLevel::Disabled: + default: + MOZ_CRASH("Invalid log level."); + return ""; + } +} + +namespace detail { + +/** + * A helper class providing reference counting for FILE*. + * It encapsulates the following: + * - the FILE handle + * - the order number it was created for when rotating (actual path) + * - number of active references + */ +class LogFile +{ + FILE* mFile; + uint32_t mFileNum; + +public: + LogFile(FILE* aFile, uint32_t aFileNum) + : mFile(aFile) + , mFileNum(aFileNum) + , mNextToRelease(nullptr) + { + } + + ~LogFile() + { + fclose(mFile); + delete mNextToRelease; + } + + FILE* File() const { return mFile; } + uint32_t Num() const { return mFileNum; } + + LogFile* mNextToRelease; +}; + +const char* +ExpandPIDMarker(const char* aFilename, char (&buffer)[2048]) +{ + MOZ_ASSERT(aFilename); + static const char kPIDToken[] = "%PID"; + const char* pidTokenPtr = strstr(aFilename, kPIDToken); + if (pidTokenPtr && + SprintfLiteral(buffer, "%.*s%s%d%s", + static_cast<int>(pidTokenPtr - aFilename), aFilename, + XRE_IsParentProcess() ? "-main." : "-child.", + base::GetCurrentProcId(), + pidTokenPtr + strlen(kPIDToken)) > 0) + { + return buffer; + } + + return aFilename; +} + +} // detail + +namespace { + // Helper method that initializes an empty va_list to be empty. + void empty_va(va_list *va, ...) + { + va_start(*va, va); + va_end(*va); + } +} + +class LogModuleManager +{ +public: + LogModuleManager() + : mModulesLock("logmodules") + , mModules(kInitialModuleCount) + , mPrintEntryCount(0) + , mOutFile(nullptr) + , mToReleaseFile(nullptr) + , mOutFileNum(0) + , mOutFilePath(strdup("")) + , mMainThread(PR_GetCurrentThread()) + , mSetFromEnv(false) + , mAddTimestamp(false) + , mIsSync(false) + , mRotate(0) + { + } + + ~LogModuleManager() + { + detail::LogFile* logFile = mOutFile.exchange(nullptr); + delete logFile; + } + + /** + * Loads config from env vars if present. + */ + void Init() + { + bool shouldAppend = false; + bool addTimestamp = false; + bool isSync = false; + int32_t rotate = 0; + const char* modules = PR_GetEnv("MOZ_LOG"); + if (!modules || !modules[0]) { + modules = PR_GetEnv("MOZ_LOG_MODULES"); + if (modules) { + NS_WARNING("MOZ_LOG_MODULES is deprecated." + "\nPlease use MOZ_LOG instead."); + } + } + if (!modules || !modules[0]) { + modules = PR_GetEnv("NSPR_LOG_MODULES"); + if (modules) { + NS_WARNING("NSPR_LOG_MODULES is deprecated." + "\nPlease use MOZ_LOG instead."); + } + } + + NSPRLogModulesParser(modules, + [&shouldAppend, &addTimestamp, &isSync, &rotate] + (const char* aName, LogLevel aLevel, int32_t aValue) mutable { + if (strcmp(aName, "append") == 0) { + shouldAppend = true; + } else if (strcmp(aName, "timestamp") == 0) { + addTimestamp = true; + } else if (strcmp(aName, "sync") == 0) { + isSync = true; + } else if (strcmp(aName, "rotate") == 0) { + rotate = (aValue << 20) / kRotateFilesNumber; + } else { + LogModule::Get(aName)->SetLevel(aLevel); + } + }); + + // Rotate implies timestamp to make the files readable + mAddTimestamp = addTimestamp || rotate > 0; + mIsSync = isSync; + mRotate = rotate; + + if (rotate > 0 && shouldAppend) { + NS_WARNING("MOZ_LOG: when you rotate the log, you cannot use append!"); + } + + const char* logFile = PR_GetEnv("MOZ_LOG_FILE"); + if (!logFile || !logFile[0]) { + logFile = PR_GetEnv("NSPR_LOG_FILE"); + } + + if (logFile && logFile[0]) { + char buf[2048]; + logFile = detail::ExpandPIDMarker(logFile, buf); + mOutFilePath.reset(strdup(logFile)); + + if (mRotate > 0) { + // Delete all the previously captured files, including non-rotated + // log files, so that users don't complain our logs eat space even + // after the rotate option has been added and don't happen to send + // us old large logs along with the rotated files. + remove(mOutFilePath.get()); + for (uint32_t i = 0; i < kRotateFilesNumber; ++i) { + RemoveFile(i); + } + } + + mOutFile = OpenFile(shouldAppend, mOutFileNum); + mSetFromEnv = true; + } + } + + void SetLogFile(const char* aFilename) + { + // For now we don't allow you to change the file at runtime. + if (mSetFromEnv) { + NS_WARNING("LogModuleManager::SetLogFile - Log file was set from the " + "MOZ_LOG_FILE environment variable."); + return; + } + + const char * filename = aFilename ? aFilename : ""; + char buf[2048]; + filename = detail::ExpandPIDMarker(filename, buf); + + // Can't use rotate at runtime yet. + MOZ_ASSERT(mRotate == 0, "We don't allow rotate for runtime logfile changes"); + mOutFilePath.reset(strdup(filename)); + + // Exchange mOutFile and set it to be released once all the writes are done. + detail::LogFile* newFile = OpenFile(false, 0); + detail::LogFile* oldFile = mOutFile.exchange(newFile); + + // Since we don't allow changing the logfile if MOZ_LOG_FILE is already set, + // and we don't allow log rotation when setting it at runtime, mToReleaseFile + // will be null, so we're not leaking. + DebugOnly<detail::LogFile*> prevFile = mToReleaseFile.exchange(oldFile); + MOZ_ASSERT(!prevFile, "Should be null because rotation is not allowed"); + + // If we just need to release a file, we must force print, in order to + // trigger the closing and release of mToReleaseFile. + if (oldFile) { + va_list va; + empty_va(&va); + Print("Logger", LogLevel::Info, "Flushing old log files\n", va); + } + } + + uint32_t GetLogFile(char *aBuffer, size_t aLength) + { + uint32_t len = strlen(mOutFilePath.get()); + if (len + 1 > aLength) { + return 0; + } + snprintf(aBuffer, aLength, "%s", mOutFilePath.get()); + return len; + } + + void SetIsSync(bool aIsSync) + { + mIsSync = aIsSync; + } + + void SetAddTimestamp(bool aAddTimestamp) + { + mAddTimestamp = aAddTimestamp; + } + + detail::LogFile* OpenFile(bool aShouldAppend, uint32_t aFileNum) + { + FILE* file; + + if (mRotate > 0) { + char buf[2048]; + SprintfLiteral(buf, "%s.%d", mOutFilePath.get(), aFileNum); + + // rotate doesn't support append. + file = fopen(buf, "w"); + } else { + file = fopen(mOutFilePath.get(), aShouldAppend ? "a" : "w"); + } + + if (!file) { + return nullptr; + } + + return new detail::LogFile(file, aFileNum); + } + + void RemoveFile(uint32_t aFileNum) + { + char buf[2048]; + SprintfLiteral(buf, "%s.%d", mOutFilePath.get(), aFileNum); + remove(buf); + } + + LogModule* CreateOrGetModule(const char* aName) + { + OffTheBooksMutexAutoLock guard(mModulesLock); + LogModule* module = nullptr; + if (!mModules.Get(aName, &module)) { + module = new LogModule(aName, LogLevel::Disabled); + mModules.Put(aName, module); + } + + return module; + } + + void Print(const char* aName, LogLevel aLevel, const char* aFmt, va_list aArgs) + { + const size_t kBuffSize = 1024; + char buff[kBuffSize]; + + char* buffToWrite = buff; + + // For backwards compat we need to use the NSPR format string versions + // of sprintf and friends and then hand off to printf. + va_list argsCopy; + va_copy(argsCopy, aArgs); + size_t charsWritten = PR_vsnprintf(buff, kBuffSize, aFmt, argsCopy); + va_end(argsCopy); + + if (charsWritten == kBuffSize - 1) { + // We may have maxed out, allocate a buffer instead. + buffToWrite = PR_vsmprintf(aFmt, aArgs); + charsWritten = strlen(buffToWrite); + } + + // Determine if a newline needs to be appended to the message. + const char* newline = ""; + if (charsWritten == 0 || buffToWrite[charsWritten - 1] != '\n') { + newline = "\n"; + } + + FILE* out = stderr; + + // In case we use rotate, this ensures the FILE is kept alive during + // its use. Increased before we load mOutFile. + ++mPrintEntryCount; + + detail::LogFile* outFile = mOutFile; + if (outFile) { + out = outFile->File(); + } + + // This differs from the NSPR format in that we do not output the + // opaque system specific thread pointer (ie pthread_t) cast + // to a long. The address of the current PR_Thread continues to be + // prefixed. + // + // Additionally we prefix the output with the abbreviated log level + // and the module name. + PRThread *currentThread = PR_GetCurrentThread(); + const char *currentThreadName = (mMainThread == currentThread) + ? "Main Thread" + : PR_GetThreadName(currentThread); + + char noNameThread[40]; + if (!currentThreadName) { + SprintfLiteral(noNameThread, "Unnamed thread %p", currentThread); + currentThreadName = noNameThread; + } + + if (!mAddTimestamp) { + fprintf_stderr(out, + "[%s]: %s/%s %s%s", + currentThreadName, ToLogStr(aLevel), + aName, buffToWrite, newline); + } else { + PRExplodedTime now; + PR_ExplodeTime(PR_Now(), PR_GMTParameters, &now); + fprintf_stderr( + out, + "%04d-%02d-%02d %02d:%02d:%02d.%06d UTC - [%s]: %s/%s %s%s", + now.tm_year, now.tm_month + 1, now.tm_mday, + now.tm_hour, now.tm_min, now.tm_sec, now.tm_usec, + currentThreadName, ToLogStr(aLevel), + aName, buffToWrite, newline); + } + + if (mIsSync) { + fflush(out); + } + + if (buffToWrite != buff) { + PR_smprintf_free(buffToWrite); + } + + if (mRotate > 0 && outFile) { + int32_t fileSize = ftell(out); + if (fileSize > mRotate) { + uint32_t fileNum = outFile->Num(); + + uint32_t nextFileNum = fileNum + 1; + if (nextFileNum >= kRotateFilesNumber) { + nextFileNum = 0; + } + + // And here is the trick. The current out-file remembers its order + // number. When no other thread shifted the global file number yet, + // we are the thread to open the next file. + if (mOutFileNum.compareExchange(fileNum, nextFileNum)) { + // We can work with mToReleaseFile because we are sure the + // mPrintEntryCount can't drop to zero now - the condition + // to actually delete what's stored in that member. + // And also, no other thread can enter this piece of code + // because mOutFile is still holding the current file with + // the non-shifted number. The compareExchange() above is + // a no-op for other threads. + outFile->mNextToRelease = mToReleaseFile; + mToReleaseFile = outFile; + + mOutFile = OpenFile(false, nextFileNum); + } + } + } + + if (--mPrintEntryCount == 0 && mToReleaseFile) { + // We were the last Print() entered, if there is a file to release + // do it now. exchange() is atomic and makes sure we release the file + // only once on one thread. + detail::LogFile* release = mToReleaseFile.exchange(nullptr); + delete release; + } + } + +private: + OffTheBooksMutex mModulesLock; + nsClassHashtable<nsCharPtrHashKey, LogModule> mModules; + + // Print() entry counter, actually reflects concurrent use of the current + // output file. ReleaseAcquire ensures that manipulation with mOutFile + // and mToReleaseFile is synchronized by manipulation with this value. + Atomic<uint32_t, ReleaseAcquire> mPrintEntryCount; + // File to write to. ReleaseAcquire because we need to sync mToReleaseFile + // with this. + Atomic<detail::LogFile*, ReleaseAcquire> mOutFile; + // File to be released when reference counter drops to zero. This member + // is assigned mOutFile when the current file has reached the limit. + // It can be Relaxed, since it's synchronized with mPrintEntryCount + // manipulation and we do atomic exchange() on it. + Atomic<detail::LogFile*, Relaxed> mToReleaseFile; + // The next file number. This is mostly only for synchronization sake. + // Can have relaxed ordering, since we only do compareExchange on it which + // is atomic regardless ordering. + Atomic<uint32_t, Relaxed> mOutFileNum; + // Just keeps the actual file path for further use. + UniqueFreePtr<char[]> mOutFilePath; + + PRThread *mMainThread; + bool mSetFromEnv; + Atomic<bool, Relaxed> mAddTimestamp; + Atomic<bool, Relaxed> mIsSync; + int32_t mRotate; +}; + +StaticAutoPtr<LogModuleManager> sLogModuleManager; + +LogModule* +LogModule::Get(const char* aName) +{ + // This is just a pass through to the LogModuleManager so + // that the LogModuleManager implementation can be kept internal. + MOZ_ASSERT(sLogModuleManager != nullptr); + return sLogModuleManager->CreateOrGetModule(aName); +} + +void +LogModule::SetLogFile(const char* aFilename) +{ + MOZ_ASSERT(sLogModuleManager); + sLogModuleManager->SetLogFile(aFilename); +} + +uint32_t +LogModule::GetLogFile(char *aBuffer, size_t aLength) +{ + MOZ_ASSERT(sLogModuleManager); + return sLogModuleManager->GetLogFile(aBuffer, aLength); +} + +void +LogModule::SetAddTimestamp(bool aAddTimestamp) +{ + sLogModuleManager->SetAddTimestamp(aAddTimestamp); +} + +void +LogModule::SetIsSync(bool aIsSync) +{ + sLogModuleManager->SetIsSync(aIsSync); +} + +void +LogModule::Init() +{ + // NB: This method is not threadsafe; it is expected to be called very early + // in startup prior to any other threads being run. + if (sLogModuleManager) { + // Already initialized. + return; + } + + // NB: We intentionally do not register for ClearOnShutdown as that happens + // before all logging is complete. And, yes, that means we leak, but + // we're doing that intentionally. + sLogModuleManager = new LogModuleManager(); + sLogModuleManager->Init(); +} + +void +LogModule::Printv(LogLevel aLevel, const char* aFmt, va_list aArgs) const +{ + MOZ_ASSERT(sLogModuleManager != nullptr); + + // Forward to LogModule manager w/ level and name + sLogModuleManager->Print(Name(), aLevel, aFmt, aArgs); +} + +} // namespace mozilla diff --git a/xpcom/base/Logging.h b/xpcom/base/Logging.h new file mode 100644 index 000000000..040fb9c49 --- /dev/null +++ b/xpcom/base/Logging.h @@ -0,0 +1,255 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_logging_h +#define mozilla_logging_h + +#include <string.h> +#include <stdarg.h> + +#include "prlog.h" + +#include "mozilla/Assertions.h" +#include "mozilla/Atomics.h" +#include "mozilla/Likely.h" +#include "mozilla/MacroForEach.h" + +// This file is a placeholder for a replacement to the NSPR logging framework +// that is defined in prlog.h. Currently it is just a pass through, but as +// work progresses more functionality will be swapped out in favor of +// mozilla logging implementations. + +// We normally have logging enabled everywhere, but measurements showed that +// having logging enabled on Android is quite expensive (hundreds of kilobytes +// for both the format strings for logging and the code to perform all the +// logging calls). Because retrieving logs from a mobile device is +// comparatively more difficult for Android than it is for desktop and because +// desktop machines tend to be less space/bandwidth-constrained than Android +// devices, we've chosen to leave logging enabled on desktop, but disabled on +// Android. Given that logging can still be useful for development purposes, +// however, we leave logging enabled on Android developer builds. +#if !defined(ANDROID) || !defined(RELEASE_OR_BETA) +#define MOZ_LOGGING_ENABLED 1 +#else +#define MOZ_LOGGING_ENABLED 0 +#endif + +namespace mozilla { + +// While not a 100% mapping to PR_LOG's numeric values, mozilla::LogLevel does +// maintain a direct mapping for the Disabled, Debug and Verbose levels. +// +// Mappings of LogLevel to PR_LOG's numeric values: +// +// +---------+------------------+-----------------+ +// | Numeric | NSPR Logging | Mozilla Logging | +// +---------+------------------+-----------------+ +// | 0 | PR_LOG_NONE | Disabled | +// | 1 | PR_LOG_ALWAYS | Error | +// | 2 | PR_LOG_ERROR | Warning | +// | 3 | PR_LOG_WARNING | Info | +// | 4 | PR_LOG_DEBUG | Debug | +// | 5 | PR_LOG_DEBUG + 1 | Verbose | +// +---------+------------------+-----------------+ +// +enum class LogLevel { + Disabled = 0, + Error, + Warning, + Info, + Debug, + Verbose, +}; + +/** + * Safely converts an integer into a valid LogLevel. + */ +LogLevel ToLogLevel(int32_t aLevel); + +class LogModule +{ +public: + ~LogModule() { ::free(mName); } + + /** + * Retrieves the module with the given name. If it does not already exist + * it will be created. + * + * @param aName The name of the module. + * @return A log module for the given name. This may be shared. + */ + static LogModule* Get(const char* aName); + + static void Init(); + + /** + * Sets the log file to the given filename. + */ + static void SetLogFile(const char* aFilename); + + /** + * @param aBuffer - pointer to a buffer + * @param aLength - the length of the buffer + * + * @return the actual length of the filepath. + */ + static uint32_t GetLogFile(char *aBuffer, size_t aLength); + + /** + * @param aAddTimestamp If we should log a time stamp with every message. + */ + static void SetAddTimestamp(bool aAddTimestamp); + + /** + * @param aIsSync If we should flush the file after every logged message. + */ + static void SetIsSync(bool aIsSync); + + /** + * Indicates whether or not the given log level is enabled. + */ + bool ShouldLog(LogLevel aLevel) const { return mLevel >= aLevel; } + + /** + * Retrieves the log module's current level. + */ + LogLevel Level() const { return mLevel; } + + /** + * Sets the log module's level. + */ + void SetLevel(LogLevel level) { mLevel = level; } + + /** + * Print a log message for this module. + */ + void Printv(LogLevel aLevel, const char* aFmt, va_list aArgs) const; + + /** + * Retrieves the module name. + */ + const char* Name() const { return mName; } + +private: + friend class LogModuleManager; + + explicit LogModule(const char* aName, LogLevel aLevel) + : mName(strdup(aName)), mLevel(aLevel) + { + } + + LogModule(LogModule&) = delete; + LogModule& operator=(const LogModule&) = delete; + + char* mName; + Atomic<LogLevel, Relaxed> mLevel; +}; + +/** + * Helper class that lazy loads the given log module. This is safe to use for + * declaring static references to log modules and can be used as a replacement + * for accessing a LogModule directly. + * + * Example usage: + * static LazyLogModule sLayoutLog("layout"); + * + * void Foo() { + * MOZ_LOG(sLayoutLog, LogLevel::Verbose, ("Entering foo")); + * } + */ +class LazyLogModule final +{ +public: + explicit constexpr LazyLogModule(const char* aLogName) + : mLogName(aLogName) + , mLog(nullptr) + { + } + + operator LogModule*() + { + // NB: The use of an atomic makes the reading and assignment of mLog + // thread-safe. There is a small chance that mLog will be set more + // than once, but that's okay as it will be set to the same LogModule + // instance each time. Also note LogModule::Get is thread-safe. + LogModule* tmp = mLog; + if (MOZ_UNLIKELY(!tmp)) { + tmp = LogModule::Get(mLogName); + mLog = tmp; + } + + return tmp; + } + +private: + const char* const mLogName; + Atomic<LogModule*, ReleaseAcquire> mLog; +}; + +namespace detail { + +inline bool log_test(const PRLogModuleInfo* module, LogLevel level) { + MOZ_ASSERT(level != LogLevel::Disabled); + return module && module->level >= static_cast<int>(level); +} + +/** + * A rather inefficient wrapper for PR_LogPrint that always allocates. + * PR_LogModuleInfo is deprecated so it's not worth the effort to do + * any better. + */ +void log_print(const PRLogModuleInfo* aModule, + LogLevel aLevel, + const char* aFmt, ...); + +inline bool log_test(const LogModule* module, LogLevel level) { + MOZ_ASSERT(level != LogLevel::Disabled); + return module && module->ShouldLog(level); +} + +void log_print(const LogModule* aModule, + LogLevel aLevel, + const char* aFmt, ...); +} // namespace detail + +} // namespace mozilla + + +// Helper macro used convert MOZ_LOG's third parameter, |_args|, from a +// parenthesized form to a varargs form. For example: +// ("%s", "a message") => "%s", "a message" +#define MOZ_LOG_EXPAND_ARGS(...) __VA_ARGS__ + +#if MOZ_LOGGING_ENABLED +#define MOZ_LOG_TEST(_module,_level) mozilla::detail::log_test(_module, _level) +#else +// Define away MOZ_LOG_TEST here so the compiler will fold away entire +// logging blocks via dead code elimination, e.g.: +// +// if (MOZ_LOG_TEST(...)) { +// ...compute things to log and log them... +// } +// +// This also has the nice property that no special definition of MOZ_LOG is +// required when logging is disabled. +#define MOZ_LOG_TEST(_module,_level) false +#endif + +#define MOZ_LOG(_module,_level,_args) \ + PR_BEGIN_MACRO \ + if (MOZ_LOG_TEST(_module,_level)) { \ + mozilla::detail::log_print(_module, _level, MOZ_LOG_EXPAND_ARGS _args); \ + } \ + PR_END_MACRO + +#undef PR_LOG +#undef PR_LOG_TEST + +// This #define is a Logging.h-only knob! Don't encourage people to get fancy +// with their log definitions by exporting it outside of Logging.h. +#undef MOZ_LOGGING_ENABLED + +#endif // mozilla_logging_h diff --git a/xpcom/base/MacHelpers.h b/xpcom/base/MacHelpers.h new file mode 100644 index 000000000..9716ae3f2 --- /dev/null +++ b/xpcom/base/MacHelpers.h @@ -0,0 +1,18 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_MacHelpers_h +#define mozilla_MacHelpers_h + +#include "nsString.h" + +namespace mozilla { + +nsresult GetSelectedCityInfo(nsAString& aCountryCode); + +} // namespace mozilla + +#endif diff --git a/xpcom/base/MacHelpers.mm b/xpcom/base/MacHelpers.mm new file mode 100644 index 000000000..19d0d8900 --- /dev/null +++ b/xpcom/base/MacHelpers.mm @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsString.h" +#include "MacHelpers.h" +#include "nsObjCExceptions.h" + +#import <Foundation/Foundation.h> + +namespace mozilla { + +nsresult +GetSelectedCityInfo(nsAString& aCountryCode) +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + // Can be replaced with [[NSLocale currentLocale] countryCode] once we build + // with the 10.12 SDK. + id countryCode = [[NSLocale currentLocale] objectForKey:NSLocaleCountryCode]; + + if (![countryCode isKindOfClass:[NSString class]]) { + return NS_ERROR_FAILURE; + } + + const char* countryCodeUTF8 = [(NSString*)countryCode UTF8String]; + + if (!countryCodeUTF8) { + return NS_ERROR_FAILURE; + } + + AppendUTF8toUTF16(countryCodeUTF8, aCountryCode); + return NS_OK; + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +} // namespace Mozilla + diff --git a/xpcom/base/NSPRLogModulesParser.cpp b/xpcom/base/NSPRLogModulesParser.cpp new file mode 100644 index 000000000..21090925c --- /dev/null +++ b/xpcom/base/NSPRLogModulesParser.cpp @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "NSPRLogModulesParser.h" + +#include "mozilla/Tokenizer.h" + +const char kDelimiters[] = ", "; +const char kAdditionalWordChars[] = "_-"; + +namespace mozilla { + +void +NSPRLogModulesParser(const char* aLogModules, + function<void(const char*, LogLevel, int32_t)> aCallback) +{ + if (!aLogModules) { + return; + } + + Tokenizer parser(aLogModules, kDelimiters, kAdditionalWordChars); + nsAutoCString moduleName; + + // Format: LOG_MODULES="Foo:2,Bar, Baz:5" + while (parser.ReadWord(moduleName)) { + // Next should be :<level>, default to Error if not provided. + LogLevel logLevel = LogLevel::Error; + int32_t levelValue = 0; + if (parser.CheckChar(':')) { + // Check if a negative value is provided. + int32_t multiplier = 1; + if (parser.CheckChar([](const char aChar) { return aChar == '-'; })) { + multiplier = -1; + } + + // NB: If a level isn't provided after the ':' we assume the default + // Error level is desired. This differs from NSPR which will stop + // processing the log module string in this case. + if (parser.ReadInteger(&levelValue)) { + logLevel = ToLogLevel(levelValue * multiplier); + } + } + + aCallback(moduleName.get(), logLevel, levelValue); + + // Skip ahead to the next token. + parser.SkipWhites(); + } +} + +} // namespace mozilla diff --git a/xpcom/base/NSPRLogModulesParser.h b/xpcom/base/NSPRLogModulesParser.h new file mode 100644 index 000000000..38aab14a3 --- /dev/null +++ b/xpcom/base/NSPRLogModulesParser.h @@ -0,0 +1,22 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Logging.h" +#include "mozilla/Function.h" + +namespace mozilla { + +/** + * Helper function that parses the legacy NSPR_LOG_MODULES env var format + * for specifying log levels and logging options. + * + * @param aLogModules The log modules configuration string. + * @param aCallback The callback to invoke for each log module config entry. + */ +void NSPRLogModulesParser(const char* aLogModules, + function<void(const char*, LogLevel, int32_t)> aCallback); + +} // namespace mozilla diff --git a/xpcom/base/OwningNonNull.h b/xpcom/base/OwningNonNull.h new file mode 100644 index 000000000..b72a250c4 --- /dev/null +++ b/xpcom/base/OwningNonNull.h @@ -0,0 +1,198 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* A class for non-null strong pointers to reference-counted objects. */ + +#ifndef mozilla_OwningNonNull_h +#define mozilla_OwningNonNull_h + +#include "nsAutoPtr.h" +#include "nsCycleCollectionNoteChild.h" + +namespace mozilla { + +template<class T> +class OwningNonNull +{ +public: + OwningNonNull() {} + + MOZ_IMPLICIT OwningNonNull(T& aValue) + { + init(&aValue); + } + + template<class U> + MOZ_IMPLICIT OwningNonNull(already_AddRefed<U>&& aValue) + { + init(aValue); + } + + template<class U> + MOZ_IMPLICIT OwningNonNull(const OwningNonNull<U>& aValue) + { + init(aValue); + } + + // This is no worse than get() in terms of const handling. + operator T&() const + { + MOZ_ASSERT(mInited); + MOZ_ASSERT(mPtr, "OwningNonNull<T> was set to null"); + return *mPtr; + } + + operator T*() const + { + MOZ_ASSERT(mInited); + MOZ_ASSERT(mPtr, "OwningNonNull<T> was set to null"); + return mPtr; + } + + // Conversion to bool is always true, so delete to catch errors + explicit operator bool() const = delete; + + T* + operator->() const + { + MOZ_ASSERT(mInited); + MOZ_ASSERT(mPtr, "OwningNonNull<T> was set to null"); + return mPtr; + } + + OwningNonNull<T>& + operator=(T* aValue) + { + init(aValue); + return *this; + } + + OwningNonNull<T>& + operator=(T& aValue) + { + init(&aValue); + return *this; + } + + template<class U> + OwningNonNull<T>& + operator=(already_AddRefed<U>&& aValue) + { + init(aValue); + return *this; + } + + template<class U> + OwningNonNull<T>& + operator=(const OwningNonNull<U>& aValue) + { + init(aValue); + return *this; + } + + // Don't allow assigning nullptr, it makes no sense + void operator=(decltype(nullptr)) = delete; + + already_AddRefed<T> forget() + { +#ifdef DEBUG + mInited = false; +#endif + return mPtr.forget(); + } + + template<class U> + void + forget(U** aOther) + { +#ifdef DEBUG + mInited = false; +#endif + mPtr.forget(aOther); + } + + // Make us work with smart pointer helpers that expect a get(). + T* get() const + { + MOZ_ASSERT(mInited); + MOZ_ASSERT(mPtr); + return mPtr; + } + + template<typename U> + void swap(U& aOther) + { + mPtr.swap(aOther); +#ifdef DEBUG + mInited = mPtr; +#endif + } + + // We have some consumers who want to check whether we're inited in non-debug + // builds as well. Luckily, we have the invariant that we're inited precisely + // when mPtr is non-null. + bool isInitialized() const + { + MOZ_ASSERT(!!mPtr == mInited, "mInited out of sync with mPtr?"); + return mPtr; + } + +protected: + template<typename U> + void init(U&& aValue) + { + mPtr = aValue; + MOZ_ASSERT(mPtr); +#ifdef DEBUG + mInited = true; +#endif + } + + RefPtr<T> mPtr; +#ifdef DEBUG + bool mInited = false; +#endif +}; + +template <typename T> +inline void +ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback, + OwningNonNull<T>& aField, + const char* aName, + uint32_t aFlags = 0) +{ + CycleCollectionNoteChild(aCallback, aField.get(), aName, aFlags); +} + +} // namespace mozilla + +// Declared in nsCOMPtr.h +template<class T> template<class U> +nsCOMPtr<T>::nsCOMPtr(const mozilla::OwningNonNull<U>& aOther) + : nsCOMPtr(aOther.get()) +{} + +template<class T> template<class U> +nsCOMPtr<T>& +nsCOMPtr<T>::operator=(const mozilla::OwningNonNull<U>& aOther) +{ + return operator=(aOther.get()); +} + +// Declared in mozilla/RefPtr.h +template<class T> template<class U> +RefPtr<T>::RefPtr(const mozilla::OwningNonNull<U>& aOther) + : RefPtr(aOther.get()) +{} + +template<class T> template<class U> +RefPtr<T>& +RefPtr<T>::operator=(const mozilla::OwningNonNull<U>& aOther) +{ + return operator=(aOther.get()); +} + +#endif // mozilla_OwningNonNull_h diff --git a/xpcom/base/StaticMutex.h b/xpcom/base/StaticMutex.h new file mode 100644 index 000000000..731e69405 --- /dev/null +++ b/xpcom/base/StaticMutex.h @@ -0,0 +1,96 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_StaticMutex_h +#define mozilla_StaticMutex_h + +#include "mozilla/Atomics.h" +#include "mozilla/Mutex.h" + +namespace mozilla { + +/** + * StaticMutex is a Mutex that can (and in fact, must) be used as a + * global/static variable. + * + * The main reason to use StaticMutex as opposed to + * StaticAutoPtr<OffTheBooksMutex> is that we instantiate the StaticMutex in a + * thread-safe manner the first time it's used. + * + * The same caveats that apply to StaticAutoPtr apply to StaticMutex. In + * particular, do not use StaticMutex as a stack variable or a class instance + * variable, because this class relies on the fact that global variablies are + * initialized to 0 in order to initialize mMutex. It is only safe to use + * StaticMutex as a global or static variable. + */ +class MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS StaticMutex +{ +public: + // In debug builds, check that mMutex is initialized for us as we expect by + // the compiler. In non-debug builds, don't declare a constructor so that + // the compiler can see that the constructor is trivial. +#ifdef DEBUG + StaticMutex() + { + MOZ_ASSERT(!mMutex); + } +#endif + + void Lock() + { + Mutex()->Lock(); + } + + void Unlock() + { + Mutex()->Unlock(); + } + + void AssertCurrentThreadOwns() + { +#ifdef DEBUG + Mutex()->AssertCurrentThreadOwns(); +#endif + } + +private: + OffTheBooksMutex* Mutex() + { + if (mMutex) { + return mMutex; + } + + OffTheBooksMutex* mutex = new OffTheBooksMutex("StaticMutex"); + if (!mMutex.compareExchange(nullptr, mutex)) { + delete mutex; + } + + return mMutex; + } + + Atomic<OffTheBooksMutex*> mMutex; + + + // Disallow copy constructor, but only in debug mode. We only define + // a default constructor in debug mode (see above); if we declared + // this constructor always, the compiler wouldn't generate a trivial + // default constructor for us in non-debug mode. +#ifdef DEBUG + StaticMutex(StaticMutex& aOther); +#endif + + // Disallow these operators. + StaticMutex& operator=(StaticMutex* aRhs); + static void* operator new(size_t) CPP_THROW_NEW; + static void operator delete(void*); +}; + +typedef BaseAutoLock<StaticMutex> StaticMutexAutoLock; +typedef BaseAutoUnlock<StaticMutex> StaticMutexAutoUnlock; + +} // namespace mozilla + +#endif diff --git a/xpcom/base/StaticPtr.h b/xpcom/base/StaticPtr.h new file mode 100644 index 000000000..f2c820a93 --- /dev/null +++ b/xpcom/base/StaticPtr.h @@ -0,0 +1,270 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_StaticPtr_h +#define mozilla_StaticPtr_h + +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/RefPtr.h" + +namespace mozilla { + +/** + * StaticAutoPtr and StaticRefPtr are like nsAutoPtr and nsRefPtr, except they + * are suitable for use as global variables. + * + * In particular, a global instance of Static{Auto,Ref}Ptr doesn't cause the + * compiler to emit a static initializer (in release builds, anyway). + * + * In order to accomplish this, Static{Auto,Ref}Ptr must have a trivial + * constructor and destructor. As a consequence, it cannot initialize its raw + * pointer to 0 on construction, and it cannot delete/release its raw pointer + * upon destruction. + * + * Since the compiler guarantees that all global variables are initialized to + * 0, these trivial constructors are safe. Since we rely on this, the clang + * plugin, run as part of our "static analysis" builds, makes it a compile-time + * error to use Static{Auto,Ref}Ptr as anything except a global variable. + * + * Static{Auto,Ref}Ptr have a limited interface as compared to ns{Auto,Ref}Ptr; + * this is intentional, since their range of acceptable uses is smaller. + */ + +template<class T> +class MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS StaticAutoPtr +{ +public: + // In debug builds, check that mRawPtr is initialized for us as we expect + // by the compiler. In non-debug builds, don't declare a constructor + // so that the compiler can see that the constructor is trivial. +#ifdef DEBUG + StaticAutoPtr() + { + MOZ_ASSERT(!mRawPtr); + } +#endif + + StaticAutoPtr<T>& operator=(T* aRhs) + { + Assign(aRhs); + return *this; + } + + T* get() const { return mRawPtr; } + + operator T*() const { return get(); } + + T* operator->() const + { + MOZ_ASSERT(mRawPtr); + return get(); + } + + T& operator*() const { return *get(); } + +private: + // Disallow copy constructor, but only in debug mode. We only define + // a default constructor in debug mode (see above); if we declared + // this constructor always, the compiler wouldn't generate a trivial + // default constructor for us in non-debug mode. +#ifdef DEBUG + StaticAutoPtr(StaticAutoPtr<T>& aOther); +#endif + + void Assign(T* aNewPtr) + { + MOZ_ASSERT(!aNewPtr || mRawPtr != aNewPtr); + T* oldPtr = mRawPtr; + mRawPtr = aNewPtr; + delete oldPtr; + } + + T* mRawPtr; +}; + +template<class T> +class MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS StaticRefPtr +{ +public: + // In debug builds, check that mRawPtr is initialized for us as we expect + // by the compiler. In non-debug builds, don't declare a constructor + // so that the compiler can see that the constructor is trivial. +#ifdef DEBUG + StaticRefPtr() + { + MOZ_ASSERT(!mRawPtr); + } +#endif + + StaticRefPtr<T>& operator=(T* aRhs) + { + AssignWithAddref(aRhs); + return *this; + } + + StaticRefPtr<T>& operator=(const StaticRefPtr<T>& aRhs) + { + return (this = aRhs.mRawPtr); + } + + StaticRefPtr<T>& operator=(already_AddRefed<T>& aRhs) + { + AssignAssumingAddRef(aRhs.take()); + return *this; + } + + StaticRefPtr<T>& operator=(already_AddRefed<T>&& aRhs) + { + AssignAssumingAddRef(aRhs.take()); + return *this; + } + + already_AddRefed<T> + forget() + { + T* temp = mRawPtr; + mRawPtr = nullptr; + return already_AddRefed<T>(temp); + } + + T* get() const { return mRawPtr; } + + operator T*() const { return get(); } + + T* operator->() const + { + MOZ_ASSERT(mRawPtr); + return get(); + } + + T& operator*() const { return *get(); } + +private: + void AssignWithAddref(T* aNewPtr) + { + if (aNewPtr) { + aNewPtr->AddRef(); + } + AssignAssumingAddRef(aNewPtr); + } + + void AssignAssumingAddRef(T* aNewPtr) + { + T* oldPtr = mRawPtr; + mRawPtr = aNewPtr; + if (oldPtr) { + oldPtr->Release(); + } + } + + T* MOZ_OWNING_REF mRawPtr; +}; + +namespace StaticPtr_internal { +class Zero; +} // namespace StaticPtr_internal + +#define REFLEXIVE_EQUALITY_OPERATORS(type1, type2, eq_fn, ...) \ + template<__VA_ARGS__> \ + inline bool \ + operator==(type1 lhs, type2 rhs) \ + { \ + return eq_fn; \ + } \ + \ + template<__VA_ARGS__> \ + inline bool \ + operator==(type2 lhs, type1 rhs) \ + { \ + return rhs == lhs; \ + } \ + \ + template<__VA_ARGS__> \ + inline bool \ + operator!=(type1 lhs, type2 rhs) \ + { \ + return !(lhs == rhs); \ + } \ + \ + template<__VA_ARGS__> \ + inline bool \ + operator!=(type2 lhs, type1 rhs) \ + { \ + return !(lhs == rhs); \ + } + +// StaticAutoPtr (in)equality operators + +template<class T, class U> +inline bool +operator==(const StaticAutoPtr<T>& aLhs, const StaticAutoPtr<U>& aRhs) +{ + return aLhs.get() == aRhs.get(); +} + +template<class T, class U> +inline bool +operator!=(const StaticAutoPtr<T>& aLhs, const StaticAutoPtr<U>& aRhs) +{ + return !(aLhs == aRhs); +} + +REFLEXIVE_EQUALITY_OPERATORS(const StaticAutoPtr<T>&, const U*, + lhs.get() == rhs, class T, class U) + +REFLEXIVE_EQUALITY_OPERATORS(const StaticAutoPtr<T>&, U*, + lhs.get() == rhs, class T, class U) + +// Let us compare StaticAutoPtr to 0. +REFLEXIVE_EQUALITY_OPERATORS(const StaticAutoPtr<T>&, StaticPtr_internal::Zero*, + lhs.get() == nullptr, class T) + +// StaticRefPtr (in)equality operators + +template<class T, class U> +inline bool +operator==(const StaticRefPtr<T>& aLhs, const StaticRefPtr<U>& aRhs) +{ + return aLhs.get() == aRhs.get(); +} + +template<class T, class U> +inline bool +operator!=(const StaticRefPtr<T>& aLhs, const StaticRefPtr<U>& aRhs) +{ + return !(aLhs == aRhs); +} + +REFLEXIVE_EQUALITY_OPERATORS(const StaticRefPtr<T>&, const U*, + lhs.get() == rhs, class T, class U) + +REFLEXIVE_EQUALITY_OPERATORS(const StaticRefPtr<T>&, U*, + lhs.get() == rhs, class T, class U) + +// Let us compare StaticRefPtr to 0. +REFLEXIVE_EQUALITY_OPERATORS(const StaticRefPtr<T>&, StaticPtr_internal::Zero*, + lhs.get() == nullptr, class T) + +#undef REFLEXIVE_EQUALITY_OPERATORS + +} // namespace mozilla + +// Declared in mozilla/RefPtr.h +template<class T> template<class U> +RefPtr<T>::RefPtr(const mozilla::StaticRefPtr<U>& aOther) + : RefPtr(aOther.get()) +{} + +template<class T> template<class U> +RefPtr<T>& +RefPtr<T>::operator=(const mozilla::StaticRefPtr<U>& aOther) +{ + return operator=(aOther.get()); +} + +#endif diff --git a/xpcom/base/SystemMemoryReporter.cpp b/xpcom/base/SystemMemoryReporter.cpp new file mode 100644 index 000000000..105b9c8cf --- /dev/null +++ b/xpcom/base/SystemMemoryReporter.cpp @@ -0,0 +1,989 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/SystemMemoryReporter.h" + +#include "mozilla/Attributes.h" +#include "mozilla/LinuxUtils.h" +#include "mozilla/PodOperations.h" +#include "mozilla/Preferences.h" +#include "mozilla/TaggedAnonymousMemory.h" +#include "mozilla/Unused.h" + +#include "nsDataHashtable.h" +#include "nsIMemoryReporter.h" +#include "nsPrintfCString.h" +#include "nsString.h" +#include "nsTHashtable.h" +#include "nsHashKeys.h" + +#include <dirent.h> +#include <inttypes.h> +#include <stdio.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> +#include <errno.h> + +// This file implements a Linux-specific, system-wide memory reporter. It +// gathers all the useful memory measurements obtainable from the OS in a +// single place, giving a high-level view of memory consumption for the entire +// machine/device. +// +// Other memory reporters measure part of a single process's memory consumption. +// This reporter is different in that it measures memory consumption of many +// processes, and they end up in a single reports tree. This is a slight abuse +// of the memory reporting infrastructure, and therefore the results are given +// their own "process" called "System", which means they show up in about:memory +// in their own section, distinct from the per-process sections. + +namespace mozilla { +namespace SystemMemoryReporter { + +#if !defined(XP_LINUX) +#error "This won't work if we're not on Linux." +#endif + +/** + * RAII helper that will close an open DIR handle. + */ +struct MOZ_STACK_CLASS AutoDir +{ + explicit AutoDir(DIR* aDir) : mDir(aDir) {} + ~AutoDir() { if (mDir) closedir(mDir); }; + DIR* mDir; +}; + +/** + * RAII helper that will close an open FILE handle. + */ +struct MOZ_STACK_CLASS AutoFile +{ + explicit AutoFile(FILE* aFile) : mFile(aFile) {} + ~AutoFile() { if (mFile) fclose(mFile); } + FILE* mFile; +}; + +static bool +EndsWithLiteral(const nsCString& aHaystack, const char* aNeedle) +{ + int32_t idx = aHaystack.RFind(aNeedle); + return idx != -1 && idx + strlen(aNeedle) == aHaystack.Length(); +} + +static void +GetDirname(const nsCString& aPath, nsACString& aOut) +{ + int32_t idx = aPath.RFind("/"); + if (idx == -1) { + aOut.Truncate(); + } else { + aOut.Assign(Substring(aPath, 0, idx)); + } +} + +static void +GetBasename(const nsCString& aPath, nsACString& aOut) +{ + nsCString out; + int32_t idx = aPath.RFind("/"); + if (idx == -1) { + out.Assign(aPath); + } else { + out.Assign(Substring(aPath, idx + 1)); + } + + // On Android, some entries in /dev/ashmem end with "(deleted)" (e.g. + // "/dev/ashmem/libxul.so(deleted)"). We don't care about this modifier, so + // cut it off when getting the entry's basename. + if (EndsWithLiteral(out, "(deleted)")) { + out.Assign(Substring(out, 0, out.RFind("(deleted)"))); + } + out.StripChars(" "); + + aOut.Assign(out); +} + +static bool +IsNumeric(const char* aStr) +{ + MOZ_ASSERT(*aStr); // shouldn't see empty strings + while (*aStr) { + if (!isdigit(*aStr)) { + return false; + } + ++aStr; + } + return true; +} + +static bool +IsAnonymous(const nsACString& aName) +{ + // Recent kernels have multiple [stack:nnnn] entries, where |nnnn| is a + // thread ID. However, the entire virtual memory area containing a thread's + // stack pointer is considered the stack for that thread, even if it was + // merged with an adjacent area containing non-stack data. So we treat them + // as regular anonymous memory. However, see below about tagged anonymous + // memory. + return aName.IsEmpty() || + StringBeginsWith(aName, NS_LITERAL_CSTRING("[stack:")); +} + +class SystemReporter final : public nsIMemoryReporter +{ + ~SystemReporter() {} + +public: + NS_DECL_THREADSAFE_ISUPPORTS + +#define REPORT(_path, _units, _amount, _desc) \ + do { \ + size_t __amount = _amount; /* evaluate _amount only once */ \ + if (__amount > 0) { \ + aHandleReport->Callback(NS_LITERAL_CSTRING("System"), _path, \ + KIND_OTHER, _units, __amount, _desc, aData); \ + } \ + } while (0) + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override + { + // There is lots of privacy-sensitive data in /proc. Just skip this + // reporter entirely when anonymization is required. + if (aAnonymize) { + return NS_OK; + } + + if (!Preferences::GetBool("memory.system_memory_reporter")) { + return NS_OK; + } + + // Read relevant fields from /proc/meminfo. + int64_t memTotal = 0, memFree = 0; + nsresult rv1 = ReadMemInfo(&memTotal, &memFree); + + // Collect per-process reports from /proc/<pid>/smaps. + int64_t totalPss = 0; + nsresult rv2 = CollectProcessReports(aHandleReport, aData, &totalPss); + + // Report the non-process numbers. + if (NS_SUCCEEDED(rv1) && NS_SUCCEEDED(rv2)) { + int64_t other = memTotal - memFree - totalPss; + REPORT(NS_LITERAL_CSTRING("mem/other"), UNITS_BYTES, other, + NS_LITERAL_CSTRING( +"Memory which is neither owned by any user-space process nor free. Note that " +"this includes memory holding cached files from the disk which can be " +"reclaimed by the OS at any time.")); + + REPORT(NS_LITERAL_CSTRING("mem/free"), UNITS_BYTES, memFree, + NS_LITERAL_CSTRING( +"Memory which is free and not being used for any purpose.")); + } + + // Report reserved memory not included in memTotal. + CollectPmemReports(aHandleReport, aData); + + // Report zram usage statistics. + CollectZramReports(aHandleReport, aData); + + // Report kgsl graphics memory usage. + CollectKgslReports(aHandleReport, aData); + + // Report ION memory usage. + CollectIonReports(aHandleReport, aData); + + return NS_OK; + } + +private: + // These are the cross-cutting measurements across all processes. + class ProcessSizes + { + public: + void Add(const nsACString& aKey, size_t aSize) + { + mTagged.Put(aKey, mTagged.Get(aKey) + aSize); + } + + void Report(nsIHandleReportCallback* aHandleReport, nsISupports* aData) + { + for (auto iter = mTagged.Iter(); !iter.Done(); iter.Next()) { + nsCStringHashKey::KeyType key = iter.Key(); + size_t amount = iter.UserData(); + + nsAutoCString path("processes/"); + path.Append(key); + + nsAutoCString desc("This is the sum of all processes' '"); + desc.Append(key); + desc.AppendLiteral("' numbers."); + + REPORT(path, UNITS_BYTES, amount, desc); + } + } + + private: + nsDataHashtable<nsCStringHashKey, size_t> mTagged; + }; + + nsresult ReadMemInfo(int64_t* aMemTotal, int64_t* aMemFree) + { + FILE* f = fopen("/proc/meminfo", "r"); + if (!f) { + return NS_ERROR_FAILURE; + } + + int n1 = fscanf(f, "MemTotal: %" SCNd64 " kB\n", aMemTotal); + int n2 = fscanf(f, "MemFree: %" SCNd64 " kB\n", aMemFree); + + fclose(f); + + if (n1 != 1 || n2 != 1) { + return NS_ERROR_FAILURE; + } + + // Convert from KB to B. + *aMemTotal *= 1024; + *aMemFree *= 1024; + + return NS_OK; + } + + nsresult CollectProcessReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, + int64_t* aTotalPss) + { + *aTotalPss = 0; + ProcessSizes processSizes; + + DIR* d = opendir("/proc"); + if (NS_WARN_IF(!d)) { + return NS_ERROR_FAILURE; + } + struct dirent* ent; + while ((ent = readdir(d))) { + struct stat statbuf; + const char* pidStr = ent->d_name; + // Don't check the return value of stat() -- it can return -1 for these + // directories even when it has succeeded, apparently. + stat(pidStr, &statbuf); + if (S_ISDIR(statbuf.st_mode) && IsNumeric(pidStr)) { + nsCString processName("process("); + + // Get the command name from cmdline. If that fails, the pid is still + // shown. + nsPrintfCString cmdlinePath("/proc/%s/cmdline", pidStr); + FILE* f = fopen(cmdlinePath.get(), "r"); + if (f) { + static const size_t len = 256; + char buf[len]; + if (fgets(buf, len, f)) { + processName.Append(buf); + // A hack: replace forward slashes with '\\' so they aren't treated + // as path separators. Consumers of this reporter (such as + // about:memory) have to undo this change. + processName.ReplaceChar('/', '\\'); + processName.AppendLiteral(", "); + } + fclose(f); + } + processName.AppendLiteral("pid="); + processName.Append(pidStr); + processName.Append(')'); + + // Read the PSS values from the smaps file. + nsPrintfCString smapsPath("/proc/%s/smaps", pidStr); + f = fopen(smapsPath.get(), "r"); + if (!f) { + // Processes can terminate between the readdir() call above and now, + // so just skip if we can't open the file. + continue; + } + ParseMappings(f, processName, aHandleReport, aData, &processSizes, + aTotalPss); + fclose(f); + + // Report the open file descriptors for this process. + nsPrintfCString procFdPath("/proc/%s/fd", pidStr); + CollectOpenFileReports(aHandleReport, aData, procFdPath, processName); + } + } + closedir(d); + + // Report the "processes/" tree. + processSizes.Report(aHandleReport, aData); + + return NS_OK; + } + + void ParseMappings(FILE* aFile, + const nsACString& aProcessName, + nsIHandleReportCallback* aHandleReport, + nsISupports* aData, + ProcessSizes* aProcessSizes, + int64_t* aTotalPss) + { + // The first line of an entry in /proc/<pid>/smaps looks just like an entry + // in /proc/<pid>/maps: + // + // address perms offset dev inode pathname + // 02366000-025d8000 rw-p 00000000 00:00 0 [heap] + // + // Each of the following lines contains a key and a value, separated + // by ": ", where the key does not contain either of those characters. + // Assuming more than this about the structure of those lines has + // failed to be future-proof in the past, so we avoid doing so. + // + // This makes it difficult to detect the start of a new entry + // until it's been removed from the stdio buffer, so we just loop + // over all lines in the file in this routine. + + const int argCount = 8; + + unsigned long long addrStart, addrEnd; + char perms[5]; + unsigned long long offset; + // The 2.6 and 3.0 kernels allocate 12 bits for the major device number and + // 20 bits for the minor device number. Future kernels might allocate more. + // 64 bits ought to be enough for anybody. + char devMajor[17]; + char devMinor[17]; + unsigned int inode; + char line[1025]; + + // This variable holds the path of the current entry, or is void + // if we're scanning for the start of a new entry. + nsAutoCString currentPath; + int pathOffset; + + currentPath.SetIsVoid(true); + while (fgets(line, sizeof(line), aFile)) { + if (currentPath.IsVoid()) { + int n = sscanf(line, + "%llx-%llx %4s %llx " + "%16[0-9a-fA-F]:%16[0-9a-fA-F] %u %n", + &addrStart, &addrEnd, perms, &offset, devMajor, + devMinor, &inode, &pathOffset); + + if (n >= argCount - 1) { + currentPath.Assign(line + pathOffset); + currentPath.StripChars("\n"); + } + continue; + } + + // Now that we have a name and other metadata, scan for the PSS. + size_t pss_kb; + int n = sscanf(line, "Pss: %zu", &pss_kb); + if (n < 1) { + continue; + } + + size_t pss = pss_kb * 1024; + if (pss > 0) { + nsAutoCString name, description, tag; + GetReporterNameAndDescription(currentPath.get(), perms, name, description, tag); + + nsAutoCString processMemPath("mem/processes/"); + processMemPath.Append(aProcessName); + processMemPath.Append('/'); + processMemPath.Append(name); + + REPORT(processMemPath, UNITS_BYTES, pss, description); + + // Increment the appropriate aProcessSizes values, and the total. + aProcessSizes->Add(tag, pss); + *aTotalPss += pss; + } + + // Now that we've seen the PSS, we're done with this entry. + currentPath.SetIsVoid(true); + } + } + + void GetReporterNameAndDescription(const char* aPath, + const char* aPerms, + nsACString& aName, + nsACString& aDesc, + nsACString& aTag) + { + aName.Truncate(); + aDesc.Truncate(); + aTag.Truncate(); + + // If aPath points to a file, we have its absolute path; it might + // also be a bracketed pseudo-name (see below). In either case + // there is also some whitespace to trim. + nsAutoCString absPath; + absPath.Append(aPath); + absPath.StripChars(" "); + + if (absPath.EqualsLiteral("[heap]")) { + aName.AppendLiteral("anonymous/brk-heap"); + aDesc.AppendLiteral( + "Memory in anonymous mappings within the boundaries defined by " + "brk() / sbrk(). This is likely to be just a portion of the " + "application's heap; the remainder lives in other anonymous mappings. " + "This corresponds to '[heap]' in /proc/<pid>/smaps."); + aTag = aName; + } else if (absPath.EqualsLiteral("[stack]")) { + aName.AppendLiteral("stack/main-thread"); + aDesc.AppendPrintf( + "The stack size of the process's main thread. This corresponds to " + "'[stack]' in /proc/<pid>/smaps."); + aTag = aName; + } else if (MozTaggedMemoryIsSupported() && + StringBeginsWith(absPath, NS_LITERAL_CSTRING("[stack:"))) { + // If tagged memory is supported, we can be reasonably sure that + // the virtual memory area containing the stack hasn't been + // merged with unrelated heap memory. (This prevents the + // "[stack:" entries from reaching the IsAnonymous case below.) + pid_t tid = atoi(absPath.get() + 7); + nsAutoCString threadName, escapedThreadName; + LinuxUtils::GetThreadName(tid, threadName); + if (threadName.IsEmpty()) { + threadName.AssignLiteral("<unknown>"); + } + escapedThreadName.Assign(threadName); + escapedThreadName.StripChars("()"); + escapedThreadName.ReplaceChar('/', '\\'); + + aName.AppendLiteral("stack/non-main-thread"); + aName.AppendLiteral("/name("); + aName.Append(escapedThreadName); + aName.Append(')'); + aTag = aName; + aName.AppendPrintf("/thread(%d)", tid); + + aDesc.AppendPrintf("The stack size of a non-main thread named '%s' with " + "thread ID %d. This corresponds to '[stack:%d]' " + "in /proc/%d/smaps.", threadName.get(), tid, tid); + } else if (absPath.EqualsLiteral("[vdso]")) { + aName.AppendLiteral("vdso"); + aDesc.AppendLiteral( + "The virtual dynamically-linked shared object, also known as the " + "'vsyscall page'. This is a memory region mapped by the operating " + "system for the purpose of allowing processes to perform some " + "privileged actions without the overhead of a syscall."); + aTag = aName; + } else if (StringBeginsWith(absPath, NS_LITERAL_CSTRING("[anon:")) && + EndsWithLiteral(absPath, "]")) { + // It's tagged memory; see also "mfbt/TaggedAnonymousMemory.h". + nsAutoCString tag(Substring(absPath, 6, absPath.Length() - 7)); + + aName.AppendLiteral("anonymous/"); + aName.Append(tag); + aTag = aName; + aDesc.AppendLiteral("Memory in anonymous mappings tagged with '"); + aDesc.Append(tag); + aDesc.Append('\''); + } else if (!IsAnonymous(absPath)) { + // We now know it's an actual file. Truncate this to its + // basename, and put the absolute path in the description. + nsAutoCString basename, dirname; + GetBasename(absPath, basename); + GetDirname(absPath, dirname); + + // Hack: A file is a shared library if the basename contains ".so" and + // its dirname contains "/lib", or if the basename ends with ".so". + if (EndsWithLiteral(basename, ".so") || + (basename.Find(".so") != -1 && dirname.Find("/lib") != -1)) { + aName.AppendLiteral("shared-libraries/"); + aTag = aName; + + if (strncmp(aPerms, "r-x", 3) == 0) { + aTag.AppendLiteral("read-executable"); + } else if (strncmp(aPerms, "rw-", 3) == 0) { + aTag.AppendLiteral("read-write"); + } else if (strncmp(aPerms, "r--", 3) == 0) { + aTag.AppendLiteral("read-only"); + } else { + aTag.AppendLiteral("other"); + } + + } else { + aName.AppendLiteral("other-files"); + if (EndsWithLiteral(basename, ".xpi")) { + aName.AppendLiteral("/extensions"); + } else if (dirname.Find("/fontconfig") != -1) { + aName.AppendLiteral("/fontconfig"); + } else { + aName.AppendLiteral("/misc"); + } + aTag = aName; + aName.Append('/'); + } + + aName.Append(basename); + aDesc.Append(absPath); + } else { + if (MozTaggedMemoryIsSupported()) { + aName.AppendLiteral("anonymous/untagged"); + aDesc.AppendLiteral("Memory in untagged anonymous mappings."); + aTag = aName; + } else { + aName.AppendLiteral("anonymous/outside-brk"); + aDesc.AppendLiteral("Memory in anonymous mappings outside the " + "boundaries defined by brk() / sbrk()."); + aTag = aName; + } + } + + aName.AppendLiteral("/["); + aName.Append(aPerms); + aName.Append(']'); + + // Append the permissions. This is useful for non-verbose mode in + // about:memory when the filename is long and goes of the right side of the + // window. + aDesc.AppendLiteral(" ["); + aDesc.Append(aPerms); + aDesc.Append(']'); + } + + void CollectPmemReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData) + { + // The pmem subsystem allocates physically contiguous memory for + // interfacing with hardware. In order to ensure availability, + // this memory is reserved during boot, and allocations are made + // within these regions at runtime. + // + // There are typically several of these pools allocated at boot. + // The /sys/kernel/pmem_regions directory contains a subdirectory + // for each one. Within each subdirectory, the files we care + // about are "size" (the total amount of physical memory) and + // "mapped_regions" (a list of the current allocations within that + // area). + DIR* d = opendir("/sys/kernel/pmem_regions"); + if (!d) { + return; + } + + struct dirent* ent; + while ((ent = readdir(d))) { + const char* name = ent->d_name; + uint64_t size; + int scanned; + + // Skip "." and ".." (and any other dotfiles). + if (name[0] == '.') { + continue; + } + + // Read the total size. The file gives the size in decimal and + // hex, in the form "13631488(0xd00000)"; we parse the former. + nsPrintfCString sizePath("/sys/kernel/pmem_regions/%s/size", name); + FILE* sizeFile = fopen(sizePath.get(), "r"); + if (NS_WARN_IF(!sizeFile)) { + continue; + } + scanned = fscanf(sizeFile, "%" SCNu64, &size); + fclose(sizeFile); + if (NS_WARN_IF(scanned != 1)) { + continue; + } + + // Read mapped regions; format described below. + uint64_t freeSize = size; + nsPrintfCString regionsPath("/sys/kernel/pmem_regions/%s/mapped_regions", + name); + FILE* regionsFile = fopen(regionsPath.get(), "r"); + if (regionsFile) { + static const size_t bufLen = 4096; + char buf[bufLen]; + while (fgets(buf, bufLen, regionsFile)) { + int pid; + + // Skip header line. + if (strncmp(buf, "pid #", 5) == 0) { + continue; + } + // Line format: "pid N:" + zero or more "(Start,Len) ". + // N is decimal; Start and Len are in hex. + scanned = sscanf(buf, "pid %d", &pid); + if (NS_WARN_IF(scanned != 1)) { + continue; + } + for (const char* nextParen = strchr(buf, '('); + nextParen != nullptr; + nextParen = strchr(nextParen + 1, '(')) { + uint64_t mapStart, mapLen; + + scanned = sscanf(nextParen + 1, "%" SCNx64 ",%" SCNx64, + &mapStart, &mapLen); + if (NS_WARN_IF(scanned != 2)) { + break; + } + + nsPrintfCString path("mem/pmem/used/%s/segment(pid=%d, " + "offset=0x%" PRIx64 ")", name, pid, mapStart); + nsPrintfCString desc("Physical memory reserved for the \"%s\" pool " + "and allocated to a buffer.", name); + REPORT(path, UNITS_BYTES, mapLen, desc); + freeSize -= mapLen; + } + } + fclose(regionsFile); + } + + nsPrintfCString path("mem/pmem/free/%s", name); + nsPrintfCString desc("Physical memory reserved for the \"%s\" pool and " + "unavailable to the rest of the system, but not " + "currently allocated.", name); + REPORT(path, UNITS_BYTES, freeSize, desc); + } + closedir(d); + } + + void + CollectIonReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData) + { + // ION is a replacement for PMEM (and other similar allocators). + // + // More details from http://lwn.net/Articles/480055/ + // "Like its PMEM-like predecessors, ION manages one or more memory pools, + // some of which are set aside at boot time to combat fragmentation or to + // serve special hardware needs. GPUs, display controllers, and cameras + // are some of the hardware blocks that may have special memory + // requirements." + // + // The file format starts as follows: + // client pid size + // ---------------------------------------------------- + // adsprpc-smd 1 4096 + // fd900000.qcom,mdss_mdp 1 1658880 + // ---------------------------------------------------- + // orphaned allocations (info is from last known client): + // Homescreen 24100 294912 0 1 + // b2g 23987 1658880 0 1 + // mdss_fb0 401 1658880 0 1 + // b2g 23987 4096 0 1 + // Built-in Keyboa 24205 61440 0 1 + // ---------------------------------------------------- + // <other stuff> + // + // For our purposes we only care about the first portion of the file noted + // above which contains memory alloations (both sections). The term + // "orphaned" is misleading, it appears that every allocation not by the + // first process is considered orphaned on FxOS devices. + + // The first three fields of each entry interest us: + // 1) client - Essentially the process name. We limit client names to 63 + // characters, in theory they should never be greater than 15 + // due to thread name length limitations. + // 2) pid - The ID of the allocating process, read as a uint32_t. + // 3) size - The size of the allocation in bytes, read as as a uint64_t. + const char* const kFormatString = "%63s %" SCNu32 " %" SCNu64; + const int kNumFields = 3; + const size_t kStringSize = 64; + const char* const kIonIommuPath = "/sys/kernel/debug/ion/iommu"; + + FILE* iommu = fopen(kIonIommuPath, "r"); + if (!iommu) { + return; + } + + AutoFile iommuGuard(iommu); + + const size_t kBufferLen = 256; + char buffer[kBufferLen]; + char client[kStringSize]; + uint32_t pid; + uint64_t size; + + // Ignore the header line. + Unused << fgets(buffer, kBufferLen, iommu); + + // Ignore the separator line. + Unused << fgets(buffer, kBufferLen, iommu); + + const char* const kSep = "----"; + const size_t kSepLen = 4; + + // Read non-orphaned entries. + while (fgets(buffer, kBufferLen, iommu) && + strncmp(kSep, buffer, kSepLen) != 0) { + if (sscanf(buffer, kFormatString, client, &pid, &size) == kNumFields) { + nsPrintfCString entryPath("ion-memory/%s (pid=%d)", client, pid); + REPORT(entryPath, UNITS_BYTES, size, + NS_LITERAL_CSTRING("An ION kernel memory allocation.")); + } + } + + // Ignore the orphaned header. + Unused << fgets(buffer, kBufferLen, iommu); + + // Read orphaned entries. + while (fgets(buffer, kBufferLen, iommu) && + strncmp(kSep, buffer, kSepLen) != 0) { + if (sscanf(buffer, kFormatString, client, &pid, &size) == kNumFields) { + nsPrintfCString entryPath("ion-memory/%s (pid=%d)", client, pid); + REPORT(entryPath, UNITS_BYTES, size, + NS_LITERAL_CSTRING("An ION kernel memory allocation.")); + } + } + + // Ignore the rest of the file. + } + + uint64_t + ReadSizeFromFile(const char* aFilename) + { + FILE* sizeFile = fopen(aFilename, "r"); + if (NS_WARN_IF(!sizeFile)) { + return 0; + } + + uint64_t size = 0; + Unused << fscanf(sizeFile, "%" SCNu64, &size); + fclose(sizeFile); + + return size; + } + + void + CollectZramReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData) + { + // zram usage stats files can be found under: + // /sys/block/zram<id> + // |--> disksize - Maximum amount of uncompressed data that can be + // stored on the disk (bytes) + // |--> orig_data_size - Uncompressed size of data in the disk (bytes) + // |--> compr_data_size - Compressed size of the data in the disk (bytes) + // |--> num_reads - Number of attempted reads to the disk (count) + // |--> num_writes - Number of attempted writes to the disk (count) + // + // Each file contains a single integer value in decimal form. + + DIR* d = opendir("/sys/block"); + if (!d) { + return; + } + + struct dirent* ent; + while ((ent = readdir(d))) { + const char* name = ent->d_name; + + // Skip non-zram entries. + if (strncmp("zram", name, 4) != 0) { + continue; + } + + // Report disk size statistics. + nsPrintfCString diskSizeFile("/sys/block/%s/disksize", name); + nsPrintfCString origSizeFile("/sys/block/%s/orig_data_size", name); + + uint64_t diskSize = ReadSizeFromFile(diskSizeFile.get()); + uint64_t origSize = ReadSizeFromFile(origSizeFile.get()); + uint64_t unusedSize = diskSize - origSize; + + nsPrintfCString diskUsedPath("zram-disksize/%s/used", name); + nsPrintfCString diskUsedDesc( + "The uncompressed size of data stored in \"%s.\" " + "This excludes zero-filled pages since " + "no memory is allocated for them.", name); + REPORT(diskUsedPath, UNITS_BYTES, origSize, diskUsedDesc); + + nsPrintfCString diskUnusedPath("zram-disksize/%s/unused", name); + nsPrintfCString diskUnusedDesc( + "The amount of uncompressed data that can still be " + "be stored in \"%s\"", name); + REPORT(diskUnusedPath, UNITS_BYTES, unusedSize, diskUnusedDesc); + + // Report disk accesses. + nsPrintfCString readsFile("/sys/block/%s/num_reads", name); + nsPrintfCString writesFile("/sys/block/%s/num_writes", name); + + uint64_t reads = ReadSizeFromFile(readsFile.get()); + uint64_t writes = ReadSizeFromFile(writesFile.get()); + + nsPrintfCString readsDesc( + "The number of reads (failed or successful) done on " + "\"%s\"", name); + nsPrintfCString readsPath("zram-accesses/%s/reads", name); + REPORT(readsPath, UNITS_COUNT_CUMULATIVE, reads, readsDesc); + + nsPrintfCString writesDesc( + "The number of writes (failed or successful) done " + "on \"%s\"", name); + nsPrintfCString writesPath("zram-accesses/%s/writes", name); + REPORT(writesPath, UNITS_COUNT_CUMULATIVE, writes, writesDesc); + + // Report compressed data size. + nsPrintfCString comprSizeFile("/sys/block/%s/compr_data_size", name); + uint64_t comprSize = ReadSizeFromFile(comprSizeFile.get()); + + nsPrintfCString comprSizeDesc( + "The compressed size of data stored in \"%s\"", + name); + nsPrintfCString comprSizePath("zram-compr-data-size/%s", name); + REPORT(comprSizePath, UNITS_BYTES, comprSize, comprSizeDesc); + } + + closedir(d); + } + + void + CollectOpenFileReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, + const nsACString& aProcPath, + const nsACString& aProcessName) + { + // All file descriptors opened by a process are listed under + // /proc/<pid>/fd/<numerical_fd>. Each entry is a symlink that points to the + // path that was opened. This can be an actual file, a socket, a pipe, an + // anon_inode, or possibly an uncategorized device. + const char kFilePrefix[] = "/"; + const char kSocketPrefix[] = "socket:"; + const char kPipePrefix[] = "pipe:"; + const char kAnonInodePrefix[] = "anon_inode:"; + + const nsCString procPath(aProcPath); + DIR* d = opendir(procPath.get()); + if (!d) { + return; + } + + char linkPath[PATH_MAX + 1]; + struct dirent* ent; + while ((ent = readdir(d))) { + const char* fd = ent->d_name; + + // Skip "." and ".." (and any other dotfiles). + if (fd[0] == '.') { + continue; + } + + nsPrintfCString fullPath("%s/%s", procPath.get(), fd); + ssize_t linkPathSize = readlink(fullPath.get(), linkPath, PATH_MAX); + if (linkPathSize > 0) { + linkPath[linkPathSize] = '\0'; + +#define CHECK_PREFIX(prefix) \ + (strncmp(linkPath, prefix, sizeof(prefix) - 1) == 0) + + const char* category = nullptr; + const char* descriptionPrefix = nullptr; + + if (CHECK_PREFIX(kFilePrefix)) { + category = "files"; // No trailing slash, the file path will have one + descriptionPrefix = "An open"; + } else if (CHECK_PREFIX(kSocketPrefix)) { + category = "sockets/"; + descriptionPrefix = "A socket"; + } else if (CHECK_PREFIX(kPipePrefix)) { + category = "pipes/"; + descriptionPrefix = "A pipe"; + } else if (CHECK_PREFIX(kAnonInodePrefix)) { + category = "anon_inodes/"; + descriptionPrefix = "An anon_inode"; + } else { + category = ""; + descriptionPrefix = "An uncategorized"; + } + +#undef CHECK_PREFIX + + const nsCString processName(aProcessName); + nsPrintfCString entryPath("open-fds/%s/%s%s/%s", + processName.get(), category, linkPath, fd); + nsPrintfCString entryDescription("%s file descriptor opened by the process", + descriptionPrefix); + REPORT(entryPath, UNITS_COUNT, 1, entryDescription); + } + } + + closedir(d); + } + + void + CollectKgslReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData) + { + // Each process that uses kgsl memory will have an entry under + // /sys/kernel/debug/kgsl/proc/<pid>/mem. This file format includes a + // header and then entries with types as follows: + // gpuaddr useraddr size id flags type usage sglen + // hexaddr hexaddr int int str str str int + // We care primarily about the usage and size. + + // For simplicity numbers will be uint64_t, strings 63 chars max. + const char* const kScanFormat = + "%" SCNx64 " %" SCNx64 " %" SCNu64 " %" SCNu64 + " %63s %63s %63s %" SCNu64; + const int kNumFields = 8; + const size_t kStringSize = 64; + + DIR* d = opendir("/sys/kernel/debug/kgsl/proc/"); + if (!d) { + return; + } + + AutoDir dirGuard(d); + + struct dirent* ent; + while ((ent = readdir(d))) { + const char* pid = ent->d_name; + + // Skip "." and ".." (and any other dotfiles). + if (pid[0] == '.') { + continue; + } + + nsPrintfCString memPath("/sys/kernel/debug/kgsl/proc/%s/mem", pid); + FILE* memFile = fopen(memPath.get(), "r"); + if (NS_WARN_IF(!memFile)) { + continue; + } + + AutoFile fileGuard(memFile); + + // Attempt to map the pid to a more useful name. + nsAutoCString procName; + LinuxUtils::GetThreadName(atoi(pid), procName); + + if (procName.IsEmpty()) { + procName.Append("pid="); + procName.Append(pid); + } else { + procName.Append(" (pid="); + procName.Append(pid); + procName.Append(")"); + } + + uint64_t gpuaddr, useraddr, size, id, sglen; + char flags[kStringSize]; + char type[kStringSize]; + char usage[kStringSize]; + + // Bypass the header line. + char buff[1024]; + Unused << fgets(buff, 1024, memFile); + + while (fscanf(memFile, kScanFormat, &gpuaddr, &useraddr, &size, &id, + flags, type, usage, &sglen) == kNumFields) { + nsPrintfCString entryPath("kgsl-memory/%s/%s", procName.get(), usage); + REPORT(entryPath, UNITS_BYTES, size, + NS_LITERAL_CSTRING("A kgsl graphics memory allocation.")); + } + } + } +}; + +NS_IMPL_ISUPPORTS(SystemReporter, nsIMemoryReporter) + +void +Init() +{ + RegisterStrongMemoryReporter(new SystemReporter()); +} + +} // namespace SystemMemoryReporter +} // namespace mozilla diff --git a/xpcom/base/SystemMemoryReporter.h b/xpcom/base/SystemMemoryReporter.h new file mode 100644 index 000000000..e52487613 --- /dev/null +++ b/xpcom/base/SystemMemoryReporter.h @@ -0,0 +1,29 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_SystemMemoryReporter_h_ +#define mozilla_SystemMemoryReporter_h_ + +namespace mozilla { +namespace SystemMemoryReporter { + +// This only works on Linux, but to make callers' lives easier, we stub out +// empty functions on other platforms. + +#if defined(XP_LINUX) +void +Init(); +#else +void +Init() +{ +} +#endif + +} // namespace SystemMemoryReporter +} // namespace mozilla + +#endif diff --git a/xpcom/base/moz.build b/xpcom/base/moz.build new file mode 100644 index 000000000..d6a336b40 --- /dev/null +++ b/xpcom/base/moz.build @@ -0,0 +1,159 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +XPIDL_SOURCES += [ + 'nsIConsoleListener.idl', + 'nsIConsoleMessage.idl', + 'nsIConsoleService.idl', + 'nsICycleCollectorListener.idl', + 'nsIDebug2.idl', + 'nsIErrorService.idl', + 'nsIException.idl', + 'nsIGZFileWriter.idl', + 'nsIInterfaceRequestor.idl', + 'nsIMemory.idl', + 'nsIMemoryInfoDumper.idl', + 'nsIMemoryReporter.idl', + 'nsIMessageLoop.idl', + 'nsIMutable.idl', + 'nsIProgrammingLanguage.idl', + 'nsISecurityConsoleMessage.idl', + 'nsIStatusReporter.idl', + 'nsISupports.idl', + 'nsIUUIDGenerator.idl', + 'nsIVersionComparator.idl', + 'nsIWeakReference.idl', + 'nsrootidl.idl', +] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + XPIDL_SOURCES += [ + 'nsIMacUtils.idl', + ] + EXPORTS.mozilla += [ + 'MacHelpers.h', + ] + UNIFIED_SOURCES += [ + 'MacHelpers.mm', + ] + +XPIDL_MODULE = 'xpcom_base' + +EXPORTS += [ + 'CodeAddressService.h', + 'ErrorList.h', + 'nsAgg.h', + 'nsAlgorithm.h', + 'nsAutoPtr.h', + 'nsAutoRef.h', + 'nsCom.h', + 'nscore.h', + 'nsCycleCollector.h', + 'nsDebugImpl.h', + 'nsDumpUtils.h', + 'nsError.h', + 'nsGZFileWriter.h', + 'nsIID.h', + 'nsInterfaceRequestorAgg.h', + 'nsISizeOf.h', + 'nsISupportsBase.h', + 'nsObjCExceptions.h', + 'nsQueryObject.h', + 'nsTraceRefcnt.h', + 'nsWeakPtr.h', +] + +if CONFIG['OS_ARCH'] == 'WINNT': + EXPORTS += [ + 'nsWindowsHelpers.h', + ] + +EXPORTS.mozilla += [ + 'AvailableMemoryTracker.h', + 'ClearOnShutdown.h', + 'CountingAllocatorBase.h', + 'CycleCollectedJSContext.h', + 'Debug.h', + 'DebuggerOnGCRunnable.h', + 'DeferredFinalize.h', + 'ErrorNames.h', + 'HoldDropJSObjects.h', + 'JSObjectHolder.h', + 'LinuxUtils.h', + 'Logging.h', + 'nsMemoryInfoDumper.h', + 'OwningNonNull.h', + 'StaticMutex.h', + 'StaticPtr.h', + 'SystemMemoryReporter.h', +] + +# nsDebugImpl isn't unified because we disable PGO so that NS_ABORT_OOM isn't +# optimized away oddly. +SOURCES += [ + 'nsDebugImpl.cpp', +] +SOURCES['nsDebugImpl.cpp'].no_pgo = True + +UNIFIED_SOURCES += [ + 'AvailableMemoryTracker.cpp', + 'ClearOnShutdown.cpp', + 'CycleCollectedJSContext.cpp', + 'Debug.cpp', + 'DebuggerOnGCRunnable.cpp', + 'DeferredFinalize.cpp', + 'ErrorNames.cpp', + 'HoldDropJSObjects.cpp', + 'JSObjectHolder.cpp', + 'Logging.cpp', + 'LogModulePrefWatcher.cpp', + 'nsConsoleMessage.cpp', + 'nsConsoleService.cpp', + 'nsCycleCollector.cpp', + 'nsCycleCollectorTraceJSHelpers.cpp', + 'nsDumpUtils.cpp', + 'nsErrorService.cpp', + 'nsGZFileWriter.cpp', + 'nsInterfaceRequestorAgg.cpp', + 'nsMemoryImpl.cpp', + 'nsMemoryInfoDumper.cpp', + 'nsMemoryReporterManager.cpp', + 'nsMessageLoop.cpp', + 'NSPRLogModulesParser.cpp', + 'nsSecurityConsoleMessage.cpp', + 'nsStatusReporterManager.cpp', + 'nsSystemInfo.cpp', + 'nsTraceRefcnt.cpp', + 'nsUUIDGenerator.cpp', + 'nsVersionComparatorImpl.cpp', +] + +if CONFIG['OS_ARCH'] == 'Linux': + SOURCES += [ + 'LinuxUtils.cpp', + 'SystemMemoryReporter.cpp', + ] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + SOURCES += [ + 'nsMacUtilsImpl.cpp', + ] +elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows': + SOURCES += [ + 'nsCrashOnException.cpp', + ] + +include('/ipc/chromium/chromium-config.mozbuild') + +FINAL_LIBRARY = 'xul' + +LOCAL_INCLUDES += [ + '../build', + '/xpcom/ds', +] + +if 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']: + CXXFLAGS += CONFIG['TK_CFLAGS'] diff --git a/xpcom/base/nsAgg.h b/xpcom/base/nsAgg.h new file mode 100644 index 000000000..8dcf8067e --- /dev/null +++ b/xpcom/base/nsAgg.h @@ -0,0 +1,336 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsAgg_h___ +#define nsAgg_h___ + +#include "nsISupports.h" +#include "nsCycleCollectionParticipant.h" + + +//////////////////////////////////////////////////////////////////////////////// + +// Put NS_DECL_AGGREGATED or NS_DECL_CYCLE_COLLECTING_AGGREGATED in your class's +// declaration. +#define NS_DECL_AGGREGATED \ + NS_DECL_ISUPPORTS \ + NS_DECL_AGGREGATED_HELPER + +#define NS_DECL_CYCLE_COLLECTING_AGGREGATED \ + NS_DECL_CYCLE_COLLECTING_ISUPPORTS \ + NS_DECL_AGGREGATED_HELPER + +#define NS_DECL_AGGREGATED_HELPER \ +public: \ + \ + /** \ + * Returns the nsISupports pointer of the inner object (aka the \ + * aggregatee). This pointer is really only useful to the outer object \ + * (aka the aggregator), which can use it to hold on to the inner \ + * object. Anything else wants the nsISupports pointer of the outer \ + * object (gotten by QI'ing inner or outer to nsISupports). This method \ + * returns a non-addrefed pointer. \ + * @return the nsISupports pointer of the inner object \ + */ \ + nsISupports* InnerObject(void) { return &fAggregated; } \ + \ + /** \ + * Returns true if this object is part of an aggregated object. \ + */ \ + bool IsPartOfAggregated(void) { return fOuter != InnerObject(); } \ + \ +private: \ + \ + /* You must implement this operation instead of the nsISupports */ \ + /* methods. */ \ + nsresult \ + AggregatedQueryInterface(const nsIID& aIID, void** aInstancePtr); \ + \ + class Internal : public nsISupports { \ + public: \ + \ + Internal() {} \ + \ + NS_IMETHOD QueryInterface(const nsIID& aIID, void** aInstancePtr); \ + NS_IMETHOD_(MozExternalRefCountType) AddRef(void); \ + NS_IMETHOD_(MozExternalRefCountType) Release(void); \ + \ + NS_DECL_OWNINGTHREAD \ + }; \ + \ + friend class Internal; \ + \ + nsISupports* MOZ_UNSAFE_REF("fOuter can either point to fAggregated " \ + "or to an outer object, and the safety " \ + "of this reference depends on the exact " \ + "lifetime semantics of the AddRef/Release " \ + "functions created by these macros.") \ + fOuter; \ + Internal fAggregated; \ + \ +public: \ + +#define NS_DECL_AGGREGATED_CYCLE_COLLECTION_CLASS(_class) \ +class NS_CYCLE_COLLECTION_INNERCLASS \ + : public nsXPCOMCycleCollectionParticipant \ +{ \ +public: \ + NS_IMETHOD_(void) Unlink(void *p) override; \ + NS_IMETHOD Traverse(void *p, nsCycleCollectionTraversalCallback &cb) \ + override; \ + NS_DECL_CYCLE_COLLECTION_CLASS_NAME_METHOD(_class) \ + NS_IMETHOD_(void) DeleteCycleCollectable(void* p) override \ + { \ + NS_CYCLE_COLLECTION_CLASSNAME(_class):: \ + Downcast(static_cast<nsISupports*>(p))->DeleteCycleCollectable(); \ + } \ + static _class* Downcast(nsISupports* s) \ + { \ + return (_class*)((char*)(s) - offsetof(_class, fAggregated)); \ + } \ + static nsISupports* Upcast(_class *p) \ + { \ + return p->InnerObject(); \ + } \ + static nsXPCOMCycleCollectionParticipant* GetParticipant() \ + { \ + return &_class::NS_CYCLE_COLLECTION_INNERNAME; \ + } \ +}; \ +NS_CHECK_FOR_RIGHT_PARTICIPANT_IMPL(_class); \ +static NS_CYCLE_COLLECTION_INNERCLASS NS_CYCLE_COLLECTION_INNERNAME; + +// Put this in your class's constructor: +#define NS_INIT_AGGREGATED(outer) \ + PR_BEGIN_MACRO \ + fOuter = outer ? outer : &fAggregated; \ + PR_END_MACRO + + +// Put this in your class's implementation file: +#define NS_IMPL_AGGREGATED(_class) \ + \ +NS_IMPL_AGGREGATED_HELPER(_class) \ + \ +NS_IMETHODIMP_(MozExternalRefCountType) \ +_class::Internal::AddRef(void) \ +{ \ + _class* agg = (_class*)((char*)(this) - offsetof(_class, fAggregated)); \ + MOZ_ASSERT(int32_t(agg->mRefCnt) >= 0, "illegal refcnt"); \ + NS_ASSERT_OWNINGTHREAD(_class); \ + ++agg->mRefCnt; \ + NS_LOG_ADDREF(this, agg->mRefCnt, #_class, sizeof(*this)); \ + return agg->mRefCnt; \ +} \ + \ +NS_IMETHODIMP_(MozExternalRefCountType) \ +_class::Internal::Release(void) \ +{ \ + _class* agg = (_class*)((char*)(this) - offsetof(_class, fAggregated)); \ + MOZ_ASSERT(int32_t(agg->mRefCnt) > 0, "dup release"); \ + NS_ASSERT_OWNINGTHREAD(_class); \ + --agg->mRefCnt; \ + NS_LOG_RELEASE(this, agg->mRefCnt, #_class); \ + if (agg->mRefCnt == 0) { \ + agg->mRefCnt = 1; /* stabilize */ \ + delete agg; \ + return 0; \ + } \ + return agg->mRefCnt; \ +} \ + +#define NS_IMPL_CYCLE_COLLECTING_AGGREGATED(_class) \ + \ +NS_IMPL_AGGREGATED_HELPER(_class) \ + \ +NS_IMETHODIMP_(MozExternalRefCountType) \ +_class::Internal::AddRef(void) \ +{ \ + _class* agg = NS_CYCLE_COLLECTION_CLASSNAME(_class)::Downcast(this); \ + MOZ_ASSERT(int32_t(agg->mRefCnt) >= 0, "illegal refcnt"); \ + NS_ASSERT_OWNINGTHREAD_AGGREGATE(agg, _class); \ + nsrefcnt count = agg->mRefCnt.incr(this); \ + NS_LOG_ADDREF(this, count, #_class, sizeof(*agg)); \ + return count; \ +} \ +NS_IMETHODIMP_(MozExternalRefCountType) \ +_class::Internal::Release(void) \ +{ \ + _class* agg = NS_CYCLE_COLLECTION_CLASSNAME(_class)::Downcast(this); \ + MOZ_ASSERT(int32_t(agg->mRefCnt) > 0, "dup release"); \ + NS_ASSERT_OWNINGTHREAD_AGGREGATE(agg, _class); \ + nsrefcnt count = agg->mRefCnt.decr(this); \ + NS_LOG_RELEASE(this, count, #_class); \ + return count; \ +} \ +NS_IMETHODIMP_(void) \ +_class::DeleteCycleCollectable(void) \ +{ \ + delete this; \ +} + +#define NS_IMPL_AGGREGATED_HELPER(_class) \ +NS_IMETHODIMP \ +_class::QueryInterface(const nsIID& aIID, void** aInstancePtr) \ +{ \ + return fOuter->QueryInterface(aIID, aInstancePtr); \ +} \ + \ +NS_IMETHODIMP_(MozExternalRefCountType) \ +_class::AddRef(void) \ +{ \ + return fOuter->AddRef(); \ +} \ + \ +NS_IMETHODIMP_(MozExternalRefCountType) \ +_class::Release(void) \ +{ \ + return fOuter->Release(); \ +} \ + \ +NS_IMETHODIMP \ +_class::Internal::QueryInterface(const nsIID& aIID, void** aInstancePtr) \ +{ \ + _class* agg = (_class*)((char*)(this) - offsetof(_class, fAggregated)); \ + return agg->AggregatedQueryInterface(aIID, aInstancePtr); \ +} \ + +/** + * To make aggregated objects participate in cycle collection we need to enable + * the outer object (aggregator) to traverse/unlink the objects held by the + * inner object (the aggregatee). We can't just make the inner object QI'able to + * NS_CYCLECOLLECTIONPARTICIPANT_IID, we don't want to return the inner object's + * nsCycleCollectionParticipant for the outer object (which will happen if the + * outer object doesn't participate in cycle collection itself). + * NS_AGGREGATED_CYCLECOLLECTIONPARTICIPANT_IID enables the outer object to get + * the inner objects nsCycleCollectionParticipant. + * + * There are three cases: + * - No aggregation + * QI'ing to NS_CYCLECOLLECTIONPARTICIPANT_IID will return the inner + * object's nsCycleCollectionParticipant. + * + * - Aggregation and outer object does not participate in cycle collection + * QI'ing to NS_CYCLECOLLECTIONPARTICIPANT_IID will not return anything. + * + * - Aggregation and outer object does participate in cycle collection + * QI'ing to NS_CYCLECOLLECTIONPARTICIPANT_IID will return the outer + * object's nsCycleCollectionParticipant. The outer object's + * nsCycleCollectionParticipant can then QI the inner object to + * NS_AGGREGATED_CYCLECOLLECTIONPARTICIPANT_IID to get the inner object's + * nsCycleCollectionParticipant, which it can use to traverse/unlink the + * objects reachable from the inner object. + */ +#define NS_AGGREGATED_CYCLECOLLECTIONPARTICIPANT_IID \ +{ \ + 0x32889b7e, \ + 0xe4fe, \ + 0x43f4, \ + { 0x85, 0x31, 0xb5, 0x28, 0x23, 0xa2, 0xe9, 0xfc } \ +} + +/** + * Just holds the IID so NS_GET_IID works. + */ +class nsAggregatedCycleCollectionParticipant +{ +public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_AGGREGATED_CYCLECOLLECTIONPARTICIPANT_IID) +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsAggregatedCycleCollectionParticipant, + NS_AGGREGATED_CYCLECOLLECTIONPARTICIPANT_IID) + +// for use with QI macros in nsISupportsUtils.h: + +#define NS_INTERFACE_MAP_BEGIN_AGGREGATED(_class) \ + NS_IMPL_AGGREGATED_QUERY_HEAD(_class) + +#define NS_INTERFACE_MAP_ENTRY_CYCLE_COLLECTION_AGGREGATED(_class) \ + NS_IMPL_QUERY_CYCLE_COLLECTION(_class) + +#define NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION_AGGREGATED(_class) \ + NS_INTERFACE_MAP_ENTRY_CYCLE_COLLECTION_AGGREGATED(_class) \ + NS_INTERFACE_MAP_ENTRY_CYCLE_COLLECTION_ISUPPORTS(_class) + +#define NS_IMPL_AGGREGATED_QUERY_HEAD(_class) \ +nsresult \ +_class::AggregatedQueryInterface(REFNSIID aIID, void** aInstancePtr) \ +{ \ + NS_ASSERTION(aInstancePtr, \ + "AggregatedQueryInterface requires a non-NULL result ptr!"); \ + if ( !aInstancePtr ) \ + return NS_ERROR_NULL_POINTER; \ + nsISupports* foundInterface; \ + if ( aIID.Equals(NS_GET_IID(nsISupports)) ) \ + foundInterface = InnerObject(); \ + else + +#define NS_IMPL_AGGREGATED_QUERY_CYCLE_COLLECTION(_class) \ + if (aIID.Equals(IsPartOfAggregated() ? \ + NS_GET_IID(nsCycleCollectionParticipant) : \ + NS_GET_IID(nsAggregatedCycleCollectionParticipant))) \ + foundInterface = NS_CYCLE_COLLECTION_PARTICIPANT(_class); \ + else + +#define NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_AGGREGATED(_class) \ + NS_IMETHODIMP \ + NS_CYCLE_COLLECTION_CLASSNAME(_class)::Traverse \ + (void *p, nsCycleCollectionTraversalCallback &cb) \ + { \ + nsISupports *s = static_cast<nsISupports*>(p); \ + MOZ_ASSERT(CheckForRightISupports(s), \ + "not the nsISupports pointer we expect"); \ + _class *tmp = static_cast<_class*>(Downcast(s)); \ + if (!tmp->IsPartOfAggregated()) \ + NS_IMPL_CYCLE_COLLECTION_DESCRIBE(_class, tmp->mRefCnt.get()) + +#define NS_GENERIC_AGGREGATED_CONSTRUCTOR(_InstanceClass) \ +static nsresult \ +_InstanceClass##Constructor(nsISupports *aOuter, REFNSIID aIID, \ + void **aResult) \ +{ \ + *aResult = nullptr; \ + if (NS_WARN_IF(aOuter && !aIID.Equals(NS_GET_IID(nsISupports)))) \ + return NS_ERROR_INVALID_ARG; \ + \ + RefPtr<_InstanceClass> inst = new _InstanceClass(aOuter); \ + if (!inst) { \ + return NS_ERROR_OUT_OF_MEMORY; \ + } \ + \ + nsISupports* inner = inst->InnerObject(); \ + nsresult rv = inner->QueryInterface(aIID, aResult); \ + \ + return rv; \ +} \ + +#define NS_GENERIC_AGGREGATED_CONSTRUCTOR_INIT(_InstanceClass, _InitMethod) \ +static nsresult \ +_InstanceClass##Constructor(nsISupports *aOuter, REFNSIID aIID, \ + void **aResult) \ +{ \ + *aResult = nullptr; \ + if (NS_WARN_IF(aOuter && !aIID.Equals(NS_GET_IID(nsISupports)))) \ + return NS_ERROR_INVALID_ARG; \ + \ + RefPtr<_InstanceClass> inst = new _InstanceClass(aOuter); \ + if (!inst) { \ + return NS_ERROR_OUT_OF_MEMORY; \ + } \ + \ + nsISupports* inner = inst->InnerObject(); \ + NS_ADDREF(inner); \ + nsresult rv = inst->_InitMethod(); \ + if (NS_SUCCEEDED(rv)) { \ + rv = inner->QueryInterface(aIID, aResult); \ + } \ + NS_RELEASE(inner); \ + \ + return rv; \ +} \ + +#endif /* nsAgg_h___ */ diff --git a/xpcom/base/nsAlgorithm.h b/xpcom/base/nsAlgorithm.h new file mode 100644 index 000000000..ceaa3ace3 --- /dev/null +++ b/xpcom/base/nsAlgorithm.h @@ -0,0 +1,75 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsAlgorithm_h___ +#define nsAlgorithm_h___ + +#include "nsCharTraits.h" // for |nsCharSourceTraits|, |nsCharSinkTraits| + +template <class T> +inline T +NS_ROUNDUP(const T& aA, const T& aB) +{ + return ((aA + (aB - 1)) / aB) * aB; +} + +// We use these instead of std::min/max because we can't include the algorithm +// header in all of XPCOM because the stl wrappers will error out when included +// in parts of XPCOM. These functions should never be used outside of XPCOM. +template <class T> +inline const T& +XPCOM_MIN(const T& aA, const T& aB) +{ + return aB < aA ? aB : aA; +} + +// Must return b when a == b in case a is -0 +template <class T> +inline const T& +XPCOM_MAX(const T& aA, const T& aB) +{ + return aA > aB ? aA : aB; +} + +namespace mozilla { + +template <class T> +inline const T& +clamped(const T& aA, const T& aMin, const T& aMax) +{ + MOZ_ASSERT(aMax >= aMin, + "clamped(): aMax must be greater than or equal to aMin"); + return XPCOM_MIN(XPCOM_MAX(aA, aMin), aMax); +} + +} // namespace mozilla + +template <class InputIterator, class T> +inline uint32_t +NS_COUNT(InputIterator& aFirst, const InputIterator& aLast, const T& aValue) +{ + uint32_t result = 0; + for (; aFirst != aLast; ++aFirst) + if (*aFirst == aValue) { + ++result; + } + return result; +} + +template <class InputIterator, class OutputIterator> +inline OutputIterator& +copy_string(const InputIterator& aFirst, const InputIterator& aLast, + OutputIterator& aResult) +{ + typedef nsCharSourceTraits<InputIterator> source_traits; + typedef nsCharSinkTraits<OutputIterator> sink_traits; + + sink_traits::write(aResult, source_traits::read(aFirst), + source_traits::readable_distance(aFirst, aLast)); + return aResult; +} + +#endif // !defined(nsAlgorithm_h___) diff --git a/xpcom/base/nsAllocator.h b/xpcom/base/nsAllocator.h new file mode 100644 index 000000000..a9ad1c70a --- /dev/null +++ b/xpcom/base/nsAllocator.h @@ -0,0 +1,17 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//////////////////////////////////////////////////////////////////////////////// +// obsolete +//////////////////////////////////////////////////////////////////////////////// + +#ifndef nsAllocator_h__ +#define nsAllocator_h__ + +#include "nsAgg.h" +#include "nsIFactory.h" + +#endif // nsAllocator_h__ diff --git a/xpcom/base/nsAutoPtr.h b/xpcom/base/nsAutoPtr.h new file mode 100644 index 000000000..b5a15000c --- /dev/null +++ b/xpcom/base/nsAutoPtr.h @@ -0,0 +1,454 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsAutoPtr_h +#define nsAutoPtr_h + +#include "nsCOMPtr.h" +#include "mozilla/RefPtr.h" +#include "mozilla/TypeTraits.h" + +#include "nsCycleCollectionNoteChild.h" +#include "mozilla/MemoryReporting.h" + +/*****************************************************************************/ + +// template <class T> class nsAutoPtrGetterTransfers; + +template <class T> +class nsAutoPtr +{ +private: + static_assert(!mozilla::IsScalar<T>::value, "If you are using " + "nsAutoPtr to hold an array, use UniquePtr<T[]> instead"); + + void** + begin_assignment() + { + assign(0); + return reinterpret_cast<void**>(&mRawPtr); + } + + void + assign(T* aNewPtr) + { + T* oldPtr = mRawPtr; + + if (aNewPtr && aNewPtr == oldPtr) { + NS_RUNTIMEABORT("Logic flaw in the caller"); + } + + mRawPtr = aNewPtr; + delete oldPtr; + } + + // |class Ptr| helps us prevent implicit "copy construction" + // through |operator T*() const| from a |const nsAutoPtr<T>| + // because two implicit conversions in a row aren't allowed. + // It still allows assignment from T* through implicit conversion + // from |T*| to |nsAutoPtr<T>::Ptr| + class Ptr + { + public: + MOZ_IMPLICIT Ptr(T* aPtr) + : mPtr(aPtr) + { + } + + operator T*() const + { + return mPtr; + } + + private: + T* MOZ_NON_OWNING_REF mPtr; + }; + +private: + T* MOZ_OWNING_REF mRawPtr; + +public: + typedef T element_type; + + ~nsAutoPtr() + { + delete mRawPtr; + } + + // Constructors + + nsAutoPtr() + : mRawPtr(0) + // default constructor + { + } + + MOZ_IMPLICIT nsAutoPtr(Ptr aRawPtr) + : mRawPtr(aRawPtr) + // construct from a raw pointer (of the right type) + { + } + + // This constructor shouldn't exist; we should just use the && + // constructor. + nsAutoPtr(nsAutoPtr<T>& aSmartPtr) + : mRawPtr(aSmartPtr.forget()) + // Construct by transferring ownership from another smart pointer. + { + } + + template <typename I> + MOZ_IMPLICIT nsAutoPtr(nsAutoPtr<I>& aSmartPtr) + : mRawPtr(aSmartPtr.forget()) + // Construct by transferring ownership from another smart pointer. + { + } + + nsAutoPtr(nsAutoPtr<T>&& aSmartPtr) + : mRawPtr(aSmartPtr.forget()) + // Construct by transferring ownership from another smart pointer. + { + } + + template <typename I> + MOZ_IMPLICIT nsAutoPtr(nsAutoPtr<I>&& aSmartPtr) + : mRawPtr(aSmartPtr.forget()) + // Construct by transferring ownership from another smart pointer. + { + } + + // Assignment operators + + nsAutoPtr<T>& + operator=(T* aRhs) + // assign from a raw pointer (of the right type) + { + assign(aRhs); + return *this; + } + + nsAutoPtr<T>& operator=(nsAutoPtr<T>& aRhs) + // assign by transferring ownership from another smart pointer. + { + assign(aRhs.forget()); + return *this; + } + + template <typename I> + nsAutoPtr<T>& operator=(nsAutoPtr<I>& aRhs) + // assign by transferring ownership from another smart pointer. + { + assign(aRhs.forget()); + return *this; + } + + nsAutoPtr<T>& operator=(nsAutoPtr<T>&& aRhs) + { + assign(aRhs.forget()); + return *this; + } + + template <typename I> + nsAutoPtr<T>& operator=(nsAutoPtr<I>&& aRhs) + { + assign(aRhs.forget()); + return *this; + } + + // Other pointer operators + + T* + get() const + /* + Prefer the implicit conversion provided automatically by + |operator T*() const|. Use |get()| _only_ to resolve + ambiguity. + */ + { + return mRawPtr; + } + + operator T*() const + /* + ...makes an |nsAutoPtr| act like its underlying raw pointer + type whenever it is used in a context where a raw pointer + is expected. It is this operator that makes an |nsAutoPtr| + substitutable for a raw pointer. + + Prefer the implicit use of this operator to calling |get()|, + except where necessary to resolve ambiguity. + */ + { + return get(); + } + + T* + forget() + { + T* temp = mRawPtr; + mRawPtr = 0; + return temp; + } + + T* + operator->() const + { + NS_PRECONDITION(mRawPtr != 0, + "You can't dereference a NULL nsAutoPtr with operator->()."); + return get(); + } + + template <typename R, typename... Args> + class Proxy + { + typedef R (T::*member_function)(Args...); + T* mRawPtr; + member_function mFunction; + public: + Proxy(T* aRawPtr, member_function aFunction) + : mRawPtr(aRawPtr), + mFunction(aFunction) + { + } + template<typename... ActualArgs> + R operator()(ActualArgs&&... aArgs) + { + return ((*mRawPtr).*mFunction)(mozilla::Forward<ActualArgs>(aArgs)...); + } + }; + + template <typename R, typename C, typename... Args> + Proxy<R, Args...> operator->*(R (C::*aFptr)(Args...)) const + { + NS_PRECONDITION(mRawPtr != 0, + "You can't dereference a NULL nsAutoPtr with operator->*()."); + return Proxy<R, Args...>(get(), aFptr); + } + + nsAutoPtr<T>* + get_address() + // This is not intended to be used by clients. See |address_of| + // below. + { + return this; + } + + const nsAutoPtr<T>* + get_address() const + // This is not intended to be used by clients. See |address_of| + // below. + { + return this; + } + +public: + T& + operator*() const + { + NS_PRECONDITION(mRawPtr != 0, + "You can't dereference a NULL nsAutoPtr with operator*()."); + return *get(); + } + + T** + StartAssignment() + { +#ifndef NSCAP_FEATURE_INLINE_STARTASSIGNMENT + return reinterpret_cast<T**>(begin_assignment()); +#else + assign(0); + return reinterpret_cast<T**>(&mRawPtr); +#endif + } +}; + +template <class T> +inline nsAutoPtr<T>* +address_of(nsAutoPtr<T>& aPtr) +{ + return aPtr.get_address(); +} + +template <class T> +inline const nsAutoPtr<T>* +address_of(const nsAutoPtr<T>& aPtr) +{ + return aPtr.get_address(); +} + +template <class T> +class nsAutoPtrGetterTransfers +/* + ... + + This class is designed to be used for anonymous temporary objects in the + argument list of calls that return COM interface pointers, e.g., + + nsAutoPtr<IFoo> fooP; + ...->GetTransferedPointer(getter_Transfers(fooP)) + + DO NOT USE THIS TYPE DIRECTLY IN YOUR CODE. Use |getter_Transfers()| instead. + + When initialized with a |nsAutoPtr|, as in the example above, it returns + a |void**|, a |T**|, or an |nsISupports**| as needed, that the + outer call (|GetTransferedPointer| in this case) can fill in. + + This type should be a nested class inside |nsAutoPtr<T>|. +*/ +{ +public: + explicit + nsAutoPtrGetterTransfers(nsAutoPtr<T>& aSmartPtr) + : mTargetSmartPtr(aSmartPtr) + { + // nothing else to do + } + + operator void**() + { + return reinterpret_cast<void**>(mTargetSmartPtr.StartAssignment()); + } + + operator T**() + { + return mTargetSmartPtr.StartAssignment(); + } + + T*& + operator*() + { + return *(mTargetSmartPtr.StartAssignment()); + } + +private: + nsAutoPtr<T>& mTargetSmartPtr; +}; + +template <class T> +inline nsAutoPtrGetterTransfers<T> +getter_Transfers(nsAutoPtr<T>& aSmartPtr) +/* + Used around a |nsAutoPtr| when + ...makes the class |nsAutoPtrGetterTransfers<T>| invisible. +*/ +{ + return nsAutoPtrGetterTransfers<T>(aSmartPtr); +} + + + +// Comparing two |nsAutoPtr|s + +template <class T, class U> +inline bool +operator==(const nsAutoPtr<T>& aLhs, const nsAutoPtr<U>& aRhs) +{ + return static_cast<const T*>(aLhs.get()) == static_cast<const U*>(aRhs.get()); +} + + +template <class T, class U> +inline bool +operator!=(const nsAutoPtr<T>& aLhs, const nsAutoPtr<U>& aRhs) +{ + return static_cast<const T*>(aLhs.get()) != static_cast<const U*>(aRhs.get()); +} + + +// Comparing an |nsAutoPtr| to a raw pointer + +template <class T, class U> +inline bool +operator==(const nsAutoPtr<T>& aLhs, const U* aRhs) +{ + return static_cast<const T*>(aLhs.get()) == static_cast<const U*>(aRhs); +} + +template <class T, class U> +inline bool +operator==(const U* aLhs, const nsAutoPtr<T>& aRhs) +{ + return static_cast<const U*>(aLhs) == static_cast<const T*>(aRhs.get()); +} + +template <class T, class U> +inline bool +operator!=(const nsAutoPtr<T>& aLhs, const U* aRhs) +{ + return static_cast<const T*>(aLhs.get()) != static_cast<const U*>(aRhs); +} + +template <class T, class U> +inline bool +operator!=(const U* aLhs, const nsAutoPtr<T>& aRhs) +{ + return static_cast<const U*>(aLhs) != static_cast<const T*>(aRhs.get()); +} + +template <class T, class U> +inline bool +operator==(const nsAutoPtr<T>& aLhs, U* aRhs) +{ + return static_cast<const T*>(aLhs.get()) == const_cast<const U*>(aRhs); +} + +template <class T, class U> +inline bool +operator==(U* aLhs, const nsAutoPtr<T>& aRhs) +{ + return const_cast<const U*>(aLhs) == static_cast<const T*>(aRhs.get()); +} + +template <class T, class U> +inline bool +operator!=(const nsAutoPtr<T>& aLhs, U* aRhs) +{ + return static_cast<const T*>(aLhs.get()) != const_cast<const U*>(aRhs); +} + +template <class T, class U> +inline bool +operator!=(U* aLhs, const nsAutoPtr<T>& aRhs) +{ + return const_cast<const U*>(aLhs) != static_cast<const T*>(aRhs.get()); +} + + + +// Comparing an |nsAutoPtr| to |nullptr| + +template <class T> +inline bool +operator==(const nsAutoPtr<T>& aLhs, decltype(nullptr)) +{ + return aLhs.get() == nullptr; +} + +template <class T> +inline bool +operator==(decltype(nullptr), const nsAutoPtr<T>& aRhs) +{ + return nullptr == aRhs.get(); +} + +template <class T> +inline bool +operator!=(const nsAutoPtr<T>& aLhs, decltype(nullptr)) +{ + return aLhs.get() != nullptr; +} + +template <class T> +inline bool +operator!=(decltype(nullptr), const nsAutoPtr<T>& aRhs) +{ + return nullptr != aRhs.get(); +} + + +/*****************************************************************************/ + +#endif // !defined(nsAutoPtr_h) diff --git a/xpcom/base/nsAutoRef.h b/xpcom/base/nsAutoRef.h new file mode 100644 index 000000000..9986c8a56 --- /dev/null +++ b/xpcom/base/nsAutoRef.h @@ -0,0 +1,672 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsAutoRef_h_ +#define nsAutoRef_h_ + +#include "mozilla/Attributes.h" + +#include "nscore.h" // for nullptr, bool + +template <class T> class nsSimpleRef; +template <class T> class nsAutoRefBase; +template <class T> class nsReturnRef; +template <class T> class nsReturningRef; + +/** + * template <class T> class nsAutoRef + * + * A class that holds a handle to a resource that must be released. + * No reference is added on construction. + * + * No copy constructor nor copy assignment operators are available, so the + * resource will be held until released on destruction or explicitly + * |reset()| or transferred through provided methods. + * + * The publicly available methods are the public methods on this class and its + * public base classes |nsAutoRefBase<T>| and |nsSimpleRef<T>|. + * + * For ref-counted resources see also |nsCountedRef<T>|. + * For function return values see |nsReturnRef<T>|. + * + * For each class |T|, |nsAutoRefTraits<T>| or |nsSimpleRef<T>| must be + * specialized to use |nsAutoRef<T>| and |nsCountedRef<T>|. + * + * @param T A class identifying the type of reference held by the + * |nsAutoRef<T>| and the unique set methods for managing references + * to the resource (defined by |nsAutoRefTraits<T>| or + * |nsSimpleRef<T>|). + * + * Often this is the class representing the resource. Sometimes a + * new possibly-incomplete class may need to be declared. + * + * + * Example: An Automatically closing file descriptor + * + * // References that are simple integral types (as file-descriptors are) + * // usually need a new class to represent the resource and how to handle its + * // references. + * class nsRawFD; + * + * // Specializing nsAutoRefTraits<nsRawFD> describes how to manage file + * // descriptors, so that nsAutoRef<nsRawFD> provides automatic closing of + * // its file descriptor on destruction. + * template <> + * class nsAutoRefTraits<nsRawFD> { + * public: + * // The file descriptor is held in an int. + * typedef int RawRef; + * // -1 means that there is no file associated with the handle. + * static int Void() { return -1; } + * // The file associated with a file descriptor is released with close(). + * static void Release(RawRef aFD) { close(aFD); } + * }; + * + * // A function returning a file descriptor that must be closed. + * nsReturnRef<nsRawFD> get_file(const char *filename) { + * // Constructing from a raw file descriptor assumes ownership. + * nsAutoRef<nsRawFD> fd(open(filename, O_RDONLY)); + * fcntl(fd, F_SETFD, FD_CLOEXEC); + * return fd.out(); + * } + * + * void f() { + * unsigned char buf[1024]; + * + * // Hold a file descriptor for /etc/hosts in fd1. + * nsAutoRef<nsRawFD> fd1(get_file("/etc/hosts")); + * + * nsAutoRef<nsRawFD> fd2; + * fd2.steal(fd1); // fd2 takes the file descriptor from fd1 + * ssize_t count = read(fd1, buf, 1024); // error fd1 has no file + * count = read(fd2, buf, 1024); // reads from /etc/hosts + * + * // If the file descriptor is not stored then it is closed. + * get_file("/etc/login.defs"); // login.defs is closed + * + * // Now use fd1 to hold a file descriptor for /etc/passwd. + * fd1 = get_file("/etc/passwd"); + * + * // The nsAutoRef<nsRawFD> can give up the file descriptor if explicitly + * // instructed, but the caller must then ensure that the file is closed. + * int rawfd = fd1.disown(); + * + * // Assume ownership of another file descriptor. + * fd1.own(open("/proc/1/maps"); + * + * // On destruction, fd1 closes /proc/1/maps and fd2 closes /etc/hosts, + * // but /etc/passwd is not closed. + * } + * + */ + + +template <class T> +class nsAutoRef : public nsAutoRefBase<T> +{ +protected: + typedef nsAutoRef<T> ThisClass; + typedef nsAutoRefBase<T> BaseClass; + typedef nsSimpleRef<T> SimpleRef; + typedef typename BaseClass::RawRefOnly RawRefOnly; + typedef typename BaseClass::LocalSimpleRef LocalSimpleRef; + +public: + nsAutoRef() + { + } + + // Explicit construction is required so as not to risk unintentionally + // releasing the resource associated with a raw ref. + explicit nsAutoRef(RawRefOnly aRefToRelease) + : BaseClass(aRefToRelease) + { + } + + // Construction from a nsReturnRef<T> function return value, which expects + // to give up ownership, transfers ownership. + // (nsReturnRef<T> is converted to const nsReturningRef<T>.) + explicit nsAutoRef(const nsReturningRef<T>& aReturning) + : BaseClass(aReturning) + { + } + + // The only assignment operator provided is for transferring from an + // nsReturnRef smart reference, which expects to pass its ownership to + // another object. + // + // With raw references and other smart references, the type of the lhs and + // its taking and releasing nature is often not obvious from an assignment + // statement. Assignment from a raw ptr especially is not normally + // expected to release the reference. + // + // Use |steal| for taking ownership from other smart refs. + // + // For raw references, use |own| to indicate intention to have the + // resource released. + // + // Or, to create another owner of the same reference, use an nsCountedRef. + + ThisClass& operator=(const nsReturningRef<T>& aReturning) + { + BaseClass::steal(aReturning.mReturnRef); + return *this; + } + + // Conversion to a raw reference allow the nsAutoRef<T> to often be used + // like a raw reference. + operator typename SimpleRef::RawRef() const + { + return this->get(); + } + + // Transfer ownership from another smart reference. + void steal(ThisClass& aOtherRef) + { + BaseClass::steal(aOtherRef); + } + + // Assume ownership of a raw ref. + // + // |own| has similar function to |steal|, and is useful for receiving + // ownership from a return value of a function. It is named differently + // because |own| requires more care to ensure that the function intends to + // give away ownership, and so that |steal| can be safely used, knowing + // that it won't steal ownership from any methods returning raw ptrs to + // data owned by a foreign object. + void own(RawRefOnly aRefToRelease) + { + BaseClass::own(aRefToRelease); + } + + // Exchange ownership with |aOther| + void swap(ThisClass& aOther) + { + LocalSimpleRef temp; + temp.SimpleRef::operator=(*this); + SimpleRef::operator=(aOther); + aOther.SimpleRef::operator=(temp); + } + + // Release the reference now. + void reset() + { + this->SafeRelease(); + LocalSimpleRef empty; + SimpleRef::operator=(empty); + } + + // Pass out the reference for a function return values. + nsReturnRef<T> out() + { + return nsReturnRef<T>(this->disown()); + } + + // operator->() and disown() are provided by nsAutoRefBase<T>. + // The default nsSimpleRef<T> provides get(). + +private: + // No copy constructor + explicit nsAutoRef(ThisClass& aRefToSteal); +}; + +/** + * template <class T> class nsCountedRef + * + * A class that creates (adds) a new reference to a resource on construction + * or assignment and releases on destruction. + * + * This class is similar to nsAutoRef<T> and inherits its methods, but also + * provides copy construction and assignment operators that enable more than + * one concurrent reference to the same resource. + * + * Specialize |nsAutoRefTraits<T>| or |nsSimpleRef<T>| to use this. This + * class assumes that the resource itself counts references and so can only be + * used when |T| represents a reference-counting resource. + */ + +template <class T> +class nsCountedRef : public nsAutoRef<T> +{ +protected: + typedef nsCountedRef<T> ThisClass; + typedef nsAutoRef<T> BaseClass; + typedef nsSimpleRef<T> SimpleRef; + typedef typename BaseClass::RawRef RawRef; + +public: + nsCountedRef() + { + } + + // Construction and assignment from a another nsCountedRef + // or a raw ref copies and increments the ref count. + nsCountedRef(const ThisClass& aRefToCopy) + { + SimpleRef::operator=(aRefToCopy); + SafeAddRef(); + } + ThisClass& operator=(const ThisClass& aRefToCopy) + { + if (this == &aRefToCopy) { + return *this; + } + + this->SafeRelease(); + SimpleRef::operator=(aRefToCopy); + SafeAddRef(); + return *this; + } + + // Implicit conversion from another smart ref argument (to a raw ref) is + // accepted here because construction and assignment safely creates a new + // reference without interfering with the reference to copy. + explicit nsCountedRef(RawRef aRefToCopy) + : BaseClass(aRefToCopy) + { + SafeAddRef(); + } + ThisClass& operator=(RawRef aRefToCopy) + { + this->own(aRefToCopy); + SafeAddRef(); + return *this; + } + + // Construction and assignment from an nsReturnRef function return value, + // which expects to give up ownership, transfers ownership. + explicit nsCountedRef(const nsReturningRef<T>& aReturning) + : BaseClass(aReturning) + { + } + ThisClass& operator=(const nsReturningRef<T>& aReturning) + { + BaseClass::operator=(aReturning); + return *this; + } + +protected: + // Increase the reference count if there is a resource. + void SafeAddRef() + { + if (this->HaveResource()) { + this->AddRef(this->get()); + } + } +}; + +/** + * template <class T> class nsReturnRef + * + * A type for function return values that hold a reference to a resource that + * must be released. See also |nsAutoRef<T>::out()|. + */ + +template <class T> +class nsReturnRef : public nsAutoRefBase<T> +{ +protected: + typedef nsAutoRefBase<T> BaseClass; + typedef typename BaseClass::RawRefOnly RawRefOnly; + +public: + // For constructing a return value with no resource + nsReturnRef() + { + } + + // For returning a smart reference from a raw reference that must be + // released. Explicit construction is required so as not to risk + // unintentionally releasing the resource associated with a raw ref. + MOZ_IMPLICIT nsReturnRef(RawRefOnly aRefToRelease) + : BaseClass(aRefToRelease) + { + } + + // Copy construction transfers ownership + nsReturnRef(nsReturnRef<T>& aRefToSteal) + : BaseClass(aRefToSteal) + { + } + + MOZ_IMPLICIT nsReturnRef(const nsReturningRef<T>& aReturning) + : BaseClass(aReturning) + { + } + + // Conversion to a temporary (const) object referring to this object so + // that the reference may be passed from a function return value + // (temporary) to another smart reference. There is no need to use this + // explicitly. Simply assign a nsReturnRef<T> function return value to a + // smart reference. + operator nsReturningRef<T>() + { + return nsReturningRef<T>(*this); + } + + // No conversion to RawRef operator is provided on nsReturnRef, to ensure + // that the return value is not carelessly assigned to a raw ptr (and the + // resource then released). If passing to a function that takes a raw + // ptr, use get or disown as appropriate. +}; + +/** + * template <class T> class nsReturningRef + * + * A class to allow ownership to be transferred from nsReturnRef function + * return values. + * + * It should not be necessary for clients to reference this + * class directly. Simply pass an nsReturnRef<T> to a parameter taking an + * |nsReturningRef<T>|. + * + * The conversion operator on nsReturnRef constructs a temporary wrapper of + * class nsReturningRef<T> around a non-const reference to the nsReturnRef. + * The wrapper can then be passed as an rvalue parameter. + */ + +template <class T> +class nsReturningRef +{ +private: + friend class nsReturnRef<T>; + + explicit nsReturningRef(nsReturnRef<T>& aReturnRef) + : mReturnRef(aReturnRef) + { + } +public: + nsReturnRef<T>& mReturnRef; +}; + +/** + * template <class T> class nsAutoRefTraits + * + * A class describing traits of references managed by the default + * |nsSimpleRef<T>| implementation and thus |nsAutoRef<T>| and |nsCountedRef|. + * The default |nsSimpleRef<T> is suitable for resources with handles that + * have a void value. (If there is no such void value for a handle, + * specialize |nsSimpleRef<T>|.) + * + * Specializations must be provided for each class |T| according to the + * following pattern: + * + * // The template parameter |T| should be a class such that the set of fields + * // in class nsAutoRefTraits<T> is unique for class |T|. Usually the + * // resource object class is sufficient. For handles that are simple + * // integral typedefs, a new unique possibly-incomplete class may need to be + * // declared. + * + * template <> + * class nsAutoRefTraits<T> + * { + * // Specializations must provide a typedef for RawRef, describing the + * // type of the handle to the resource. + * typedef <handle-type> RawRef; + * + * // Specializations should define Void(), a function returning a value + * // suitable for a handle that does not have an associated resource. + * // + * // The return type must be a suitable as the parameter to a RawRef + * // constructor and operator==. + * // + * // If this method is not accessible then some limited nsAutoRef + * // functionality will still be available, but the default constructor, + * // |reset|, and most transfer of ownership methods will not be available. + * static <return-type> Void(); + * + * // Specializations must define Release() to properly finalize the + * // handle to a non-void custom-deleted or reference-counted resource. + * static void Release(RawRef aRawRef); + * + * // For reference-counted resources, if |nsCountedRef<T>| is to be used, + * // specializations must define AddRef to increment the reference count + * // held by a non-void handle. + * // (AddRef() is not necessary for |nsAutoRef<T>|.) + * static void AddRef(RawRef aRawRef); + * }; + * + * See nsPointerRefTraits for example specializations for simple pointer + * references. See nsAutoRef for an example specialization for a non-pointer + * reference. + */ + +template <class T> class nsAutoRefTraits; + +/** + * template <class T> class nsPointerRefTraits + * + * A convenience class useful as a base class for specializations of + * |nsAutoRefTraits<T>| where the handle to the resource is a pointer to |T|. + * By inheriting from this class, definitions of only Release(RawRef) and + * possibly AddRef(RawRef) need to be added. + * + * Examples of use: + * + * template <> + * class nsAutoRefTraits<PRFileDesc> : public nsPointerRefTraits<PRFileDesc> + * { + * public: + * static void Release(PRFileDesc *ptr) { PR_Close(ptr); } + * }; + * + * template <> + * class nsAutoRefTraits<FcPattern> : public nsPointerRefTraits<FcPattern> + * { + * public: + * static void Release(FcPattern *ptr) { FcPatternDestroy(ptr); } + * static void AddRef(FcPattern *ptr) { FcPatternReference(ptr); } + * }; + */ + +template <class T> +class nsPointerRefTraits +{ +public: + // The handle is a pointer to T. + typedef T* RawRef; + // A nullptr does not have a resource. + static RawRef Void() + { + return nullptr; + } +}; + +/** + * template <class T> class nsSimpleRef + * + * Constructs a non-smart reference, and provides methods to test whether + * there is an associated resource and (if so) get its raw handle. + * + * A default implementation is suitable for resources with handles that have a + * void value. This is not intended for direct use but used by |nsAutoRef<T>| + * and thus |nsCountedRef<T>|. + * + * Specialize this class if there is no particular void value for the resource + * handle. A specialized implementation must also provide Release(RawRef), + * and, if |nsCountedRef<T>| is required, AddRef(RawRef), as described in + * nsAutoRefTraits<T>. + */ + +template <class T> +class nsSimpleRef : protected nsAutoRefTraits<T> +{ +protected: + // The default implementation uses nsAutoRefTrait<T>. + // Specializations need not define this typedef. + typedef nsAutoRefTraits<T> Traits; + // The type of the handle to the resource. + // A specialization must provide a typedef for RawRef. + typedef typename Traits::RawRef RawRef; + + // Construct with no resource. + // + // If this constructor is not accessible then some limited nsAutoRef + // functionality will still be available, but the default constructor, + // |reset|, and most transfer of ownership methods will not be available. + nsSimpleRef() + : mRawRef(Traits::Void()) + { + } + // Construct with a handle to a resource. + // A specialization must provide this. + explicit nsSimpleRef(RawRef aRawRef) + : mRawRef(aRawRef) + { + } + + // Test whether there is an associated resource. A specialization must + // provide this. The function is permitted to always return true if the + // default constructor is not accessible, or if Release (and AddRef) can + // deal with void handles. + bool HaveResource() const + { + return mRawRef != Traits::Void(); + } + +public: + // A specialization must provide get() or loose some functionality. This + // is inherited by derived classes and the specialization may choose + // whether it is public or protected. + RawRef get() const + { + return mRawRef; + } + +private: + RawRef mRawRef; +}; + + +/** + * template <class T> class nsAutoRefBase + * + * Internal base class for |nsAutoRef<T>| and |nsReturnRef<T>|. + * Adds release on destruction to a |nsSimpleRef<T>|. + */ + +template <class T> +class nsAutoRefBase : public nsSimpleRef<T> +{ +protected: + typedef nsAutoRefBase<T> ThisClass; + typedef nsSimpleRef<T> SimpleRef; + typedef typename SimpleRef::RawRef RawRef; + + nsAutoRefBase() + { + } + + // A type for parameters that should be passed a raw ref but should not + // accept implicit conversions (from another smart ref). (The only + // conversion to this type is from a raw ref so only raw refs will be + // accepted.) + class RawRefOnly + { + public: + MOZ_IMPLICIT RawRefOnly(RawRef aRawRef) + : mRawRef(aRawRef) + { + } + operator RawRef() const + { + return mRawRef; + } + private: + RawRef mRawRef; + }; + + // Construction from a raw ref assumes ownership + explicit nsAutoRefBase(RawRefOnly aRefToRelease) + : SimpleRef(aRefToRelease) + { + } + + // Constructors that steal ownership + explicit nsAutoRefBase(ThisClass& aRefToSteal) + : SimpleRef(aRefToSteal.disown()) + { + } + explicit nsAutoRefBase(const nsReturningRef<T>& aReturning) + : SimpleRef(aReturning.mReturnRef.disown()) + { + } + + ~nsAutoRefBase() + { + SafeRelease(); + } + + // An internal class providing access to protected nsSimpleRef<T> + // constructors for construction of temporary simple references (that are + // not ThisClass). + class LocalSimpleRef : public SimpleRef + { + public: + LocalSimpleRef() + { + } + explicit LocalSimpleRef(RawRef aRawRef) + : SimpleRef(aRawRef) + { + } + }; + +private: + ThisClass& operator=(const ThisClass& aSmartRef) = delete; + +public: + RawRef operator->() const + { + return this->get(); + } + + // Transfer ownership to a raw reference. + // + // THE CALLER MUST ENSURE THAT THE REFERENCE IS EXPLICITLY RELEASED. + // + // Is this really what you want to use? Using this removes any guarantee + // of release. Use nsAutoRef<T>::out() for return values, or an + // nsAutoRef<T> modifiable lvalue for an out parameter. Use disown() when + // the reference must be stored in a POD type object, such as may be + // preferred for a namespace-scope object with static storage duration, + // for example. + RawRef disown() + { + RawRef temp = this->get(); + LocalSimpleRef empty; + SimpleRef::operator=(empty); + return temp; + } + +protected: + // steal and own are protected because they make no sense on nsReturnRef, + // but steal is implemented on this class for access to aOtherRef.disown() + // when aOtherRef is an nsReturnRef; + + // Transfer ownership from another smart reference. + void steal(ThisClass& aOtherRef) + { + own(aOtherRef.disown()); + } + // Assume ownership of a raw ref. + void own(RawRefOnly aRefToRelease) + { + SafeRelease(); + LocalSimpleRef ref(aRefToRelease); + SimpleRef::operator=(ref); + } + + // Release a resource if there is one. + void SafeRelease() + { + if (this->HaveResource()) { + this->Release(this->get()); + } + } +}; + +#endif // !defined(nsAutoRef_h_) diff --git a/xpcom/base/nsCom.h b/xpcom/base/nsCom.h new file mode 100644 index 000000000..20353a266 --- /dev/null +++ b/xpcom/base/nsCom.h @@ -0,0 +1,12 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsCom_h__ +#define nsCom_h__ +#include "nscore.h" +#endif + + diff --git a/xpcom/base/nsConsoleMessage.cpp b/xpcom/base/nsConsoleMessage.cpp new file mode 100644 index 000000000..e3530745b --- /dev/null +++ b/xpcom/base/nsConsoleMessage.cpp @@ -0,0 +1,56 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Base implementation for console messages. + */ + +#include "nsConsoleMessage.h" +#include "jsapi.h" + +NS_IMPL_ISUPPORTS(nsConsoleMessage, nsIConsoleMessage) + +nsConsoleMessage::nsConsoleMessage() + : mTimeStamp(0) + , mMessage() +{ +} + +nsConsoleMessage::nsConsoleMessage(const char16_t* aMessage) +{ + mTimeStamp = JS_Now() / 1000; + mMessage.Assign(aMessage); +} + +NS_IMETHODIMP +nsConsoleMessage::GetMessageMoz(char16_t** aResult) +{ + *aResult = ToNewUnicode(mMessage); + + return NS_OK; +} + +NS_IMETHODIMP +nsConsoleMessage::GetLogLevel(uint32_t* aLogLevel) +{ + *aLogLevel = nsConsoleMessage::info; + return NS_OK; +} + +NS_IMETHODIMP +nsConsoleMessage::GetTimeStamp(int64_t* aTimeStamp) +{ + *aTimeStamp = mTimeStamp; + return NS_OK; +} + +NS_IMETHODIMP +nsConsoleMessage::ToString(nsACString& /*UTF8*/ aResult) +{ + CopyUTF16toUTF8(mMessage, aResult); + + return NS_OK; +} diff --git a/xpcom/base/nsConsoleMessage.h b/xpcom/base/nsConsoleMessage.h new file mode 100644 index 000000000..64ad921a5 --- /dev/null +++ b/xpcom/base/nsConsoleMessage.h @@ -0,0 +1,33 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __nsconsolemessage_h__ +#define __nsconsolemessage_h__ + +#include "mozilla/Attributes.h" + +#include "nsIConsoleMessage.h" +#include "nsString.h" + +class nsConsoleMessage final : public nsIConsoleMessage +{ +public: + nsConsoleMessage(); + explicit nsConsoleMessage(const char16_t* aMessage); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSICONSOLEMESSAGE + +private: + ~nsConsoleMessage() + { + } + + int64_t mTimeStamp; + nsString mMessage; +}; + +#endif /* __nsconsolemessage_h__ */ diff --git a/xpcom/base/nsConsoleService.cpp b/xpcom/base/nsConsoleService.cpp new file mode 100644 index 000000000..3862a02c4 --- /dev/null +++ b/xpcom/base/nsConsoleService.cpp @@ -0,0 +1,473 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Maintains a circular buffer of recent messages, and notifies + * listeners when new messages are logged. + */ + +/* Threadsafe. */ + +#include "nsMemory.h" +#include "nsCOMArray.h" +#include "nsThreadUtils.h" + +#include "nsConsoleService.h" +#include "nsConsoleMessage.h" +#include "nsIClassInfoImpl.h" +#include "nsIConsoleListener.h" +#include "nsPrintfCString.h" +#include "nsProxyRelease.h" +#include "nsIScriptError.h" +#include "nsISupportsPrimitives.h" + +#include "mozilla/Preferences.h" + +#if defined(ANDROID) +#include <android/log.h> +#include "mozilla/dom/ContentChild.h" +#endif +#ifdef XP_WIN +#include <windows.h> +#endif + +#ifdef MOZ_TASK_TRACER +#include "GeckoTaskTracer.h" +using namespace mozilla::tasktracer; +#endif + +using namespace mozilla; + +NS_IMPL_ADDREF(nsConsoleService) +NS_IMPL_RELEASE(nsConsoleService) +NS_IMPL_CLASSINFO(nsConsoleService, nullptr, + nsIClassInfo::THREADSAFE | nsIClassInfo::SINGLETON, + NS_CONSOLESERVICE_CID) +NS_IMPL_QUERY_INTERFACE_CI(nsConsoleService, nsIConsoleService, nsIObserver) +NS_IMPL_CI_INTERFACE_GETTER(nsConsoleService, nsIConsoleService, nsIObserver) + +static bool sLoggingEnabled = true; +static bool sLoggingBuffered = true; +#if defined(ANDROID) +static bool sLoggingLogcat = true; +#endif // defined(ANDROID) + +nsConsoleService::MessageElement::~MessageElement() +{ +} + +nsConsoleService::nsConsoleService() + : mCurrentSize(0) + , mDeliveringMessage(false) + , mLock("nsConsoleService.mLock") +{ + // XXX grab this from a pref! + // hm, but worry about circularity, bc we want to be able to report + // prefs errs... + mMaximumSize = 250; +} + + +void +nsConsoleService::ClearMessagesForWindowID(const uint64_t innerID) +{ + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + MutexAutoLock lock(mLock); + + for (MessageElement* e = mMessages.getFirst(); e != nullptr; ) { + // Only messages implementing nsIScriptError interface expose the + // inner window ID. + nsCOMPtr<nsIScriptError> scriptError = do_QueryInterface(e->Get()); + if (!scriptError) { + e = e->getNext(); + continue; + } + uint64_t innerWindowID; + nsresult rv = scriptError->GetInnerWindowID(&innerWindowID); + if (NS_FAILED(rv) || innerWindowID != innerID) { + e = e->getNext(); + continue; + } + + MessageElement* next = e->getNext(); + e->remove(); + delete e; + mCurrentSize--; + MOZ_ASSERT(mCurrentSize < mMaximumSize); + + e = next; + } +} + +void +nsConsoleService::ClearMessages() +{ + // NB: A lock is not required here as it's only called from |Reset| which + // locks for us and from the dtor. + while (!mMessages.isEmpty()) { + MessageElement* e = mMessages.popFirst(); + delete e; + } + mCurrentSize = 0; +} + +nsConsoleService::~nsConsoleService() +{ + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + + ClearMessages(); +} + +class AddConsolePrefWatchers : public Runnable +{ +public: + explicit AddConsolePrefWatchers(nsConsoleService* aConsole) : mConsole(aConsole) + { + } + + NS_IMETHOD Run() override + { + Preferences::AddBoolVarCache(&sLoggingEnabled, "consoleservice.enabled", true); + Preferences::AddBoolVarCache(&sLoggingBuffered, "consoleservice.buffered", true); +#if defined(ANDROID) + Preferences::AddBoolVarCache(&sLoggingLogcat, "consoleservice.logcat", true); +#endif // defined(ANDROID) + + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + MOZ_ASSERT(obs); + obs->AddObserver(mConsole, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); + obs->AddObserver(mConsole, "inner-window-destroyed", false); + + if (!sLoggingBuffered) { + mConsole->Reset(); + } + return NS_OK; + } + +private: + RefPtr<nsConsoleService> mConsole; +}; + +nsresult +nsConsoleService::Init() +{ + NS_DispatchToMainThread(new AddConsolePrefWatchers(this)); + + return NS_OK; +} + +namespace { + +class LogMessageRunnable : public Runnable +{ +public: + LogMessageRunnable(nsIConsoleMessage* aMessage, nsConsoleService* aService) + : mMessage(aMessage) + , mService(aService) + { } + + NS_DECL_NSIRUNNABLE + +private: + nsCOMPtr<nsIConsoleMessage> mMessage; + RefPtr<nsConsoleService> mService; +}; + +NS_IMETHODIMP +LogMessageRunnable::Run() +{ + MOZ_ASSERT(NS_IsMainThread()); + + // Snapshot of listeners so that we don't reenter this hash during + // enumeration. + nsCOMArray<nsIConsoleListener> listeners; + mService->CollectCurrentListeners(listeners); + + mService->SetIsDelivering(); + + for (int32_t i = 0; i < listeners.Count(); ++i) { + listeners[i]->Observe(mMessage); + } + + mService->SetDoneDelivering(); + + return NS_OK; +} + +} // namespace + +// nsIConsoleService methods +NS_IMETHODIMP +nsConsoleService::LogMessage(nsIConsoleMessage* aMessage) +{ + return LogMessageWithMode(aMessage, OutputToLog); +} + +// This can be called off the main thread. +nsresult +nsConsoleService::LogMessageWithMode(nsIConsoleMessage* aMessage, + nsConsoleService::OutputMode aOutputMode) +{ + if (!aMessage) { + return NS_ERROR_INVALID_ARG; + } + + if (!sLoggingEnabled) { + return NS_OK; + } + + if (NS_IsMainThread() && mDeliveringMessage) { + nsCString msg; + aMessage->ToString(msg); + NS_WARNING(nsPrintfCString("Reentrancy error: some client attempted " + "to display a message to the console while in a console listener. " + "The following message was discarded: \"%s\"", msg.get()).get()); + return NS_ERROR_FAILURE; + } + + RefPtr<LogMessageRunnable> r; + nsCOMPtr<nsIConsoleMessage> retiredMessage; + + /* + * Lock while updating buffer, and while taking snapshot of + * listeners array. + */ + { + MutexAutoLock lock(mLock); + +#if defined(ANDROID) + if (sLoggingLogcat && aOutputMode == OutputToLog) { + nsCString msg; + aMessage->ToString(msg); + + /** Attempt to use the process name as the log tag. */ + mozilla::dom::ContentChild* child = + mozilla::dom::ContentChild::GetSingleton(); + nsCString appName; + if (child) { + child->GetProcessName(appName); + } else { + appName = "GeckoConsole"; + } + + uint32_t logLevel = 0; + aMessage->GetLogLevel(&logLevel); + + android_LogPriority logPriority = ANDROID_LOG_INFO; + switch (logLevel) { + case nsIConsoleMessage::debug: + logPriority = ANDROID_LOG_DEBUG; + break; + case nsIConsoleMessage::info: + logPriority = ANDROID_LOG_INFO; + break; + case nsIConsoleMessage::warn: + logPriority = ANDROID_LOG_WARN; + break; + case nsIConsoleMessage::error: + logPriority = ANDROID_LOG_ERROR; + break; + } + + __android_log_print(logPriority, appName.get(), "%s", msg.get()); + } +#endif +#ifdef XP_WIN + if (IsDebuggerPresent()) { + nsString msg; + aMessage->GetMessageMoz(getter_Copies(msg)); + msg.Append('\n'); + OutputDebugStringW(msg.get()); + } +#endif +#ifdef MOZ_TASK_TRACER + { + nsCString msg; + aMessage->ToString(msg); + int prefixPos = msg.Find(GetJSLabelPrefix()); + if (prefixPos >= 0) { + nsDependentCSubstring submsg(msg, prefixPos); + AddLabel("%s", submsg.BeginReading()); + } + } +#endif + + if (sLoggingBuffered) { + MessageElement* e = new MessageElement(aMessage); + mMessages.insertBack(e); + if (mCurrentSize != mMaximumSize) { + mCurrentSize++; + } else { + MessageElement* p = mMessages.popFirst(); + MOZ_ASSERT(p); + p->swapMessage(retiredMessage); + delete p; + } + } + + if (mListeners.Count() > 0) { + r = new LogMessageRunnable(aMessage, this); + } + } + + if (retiredMessage) { + // Release |retiredMessage| on the main thread in case it is an instance of + // a mainthread-only class like nsScriptErrorWithStack and we're off the + // main thread. + NS_ReleaseOnMainThread(retiredMessage.forget()); + } + + if (r) { + // avoid failing in XPCShell tests + nsCOMPtr<nsIThread> mainThread = do_GetMainThread(); + if (mainThread) { + NS_DispatchToMainThread(r.forget()); + } + } + + return NS_OK; +} + +void +nsConsoleService::CollectCurrentListeners( + nsCOMArray<nsIConsoleListener>& aListeners) +{ + MutexAutoLock lock(mLock); + for (auto iter = mListeners.Iter(); !iter.Done(); iter.Next()) { + nsIConsoleListener* value = iter.UserData(); + aListeners.AppendObject(value); + } +} + +NS_IMETHODIMP +nsConsoleService::LogStringMessage(const char16_t* aMessage) +{ + if (!sLoggingEnabled) { + return NS_OK; + } + + RefPtr<nsConsoleMessage> msg(new nsConsoleMessage(aMessage)); + return this->LogMessage(msg); +} + +NS_IMETHODIMP +nsConsoleService::GetMessageArray(uint32_t* aCount, + nsIConsoleMessage*** aMessages) +{ + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + + MutexAutoLock lock(mLock); + + if (mMessages.isEmpty()) { + /* + * Make a 1-length output array so that nobody gets confused, + * and return a count of 0. This should result in a 0-length + * array object when called from script. + */ + nsIConsoleMessage** messageArray = (nsIConsoleMessage**) + moz_xmalloc(sizeof(nsIConsoleMessage*)); + *messageArray = nullptr; + *aMessages = messageArray; + *aCount = 0; + + return NS_OK; + } + + MOZ_ASSERT(mCurrentSize <= mMaximumSize); + nsIConsoleMessage** messageArray = + static_cast<nsIConsoleMessage**>(moz_xmalloc(sizeof(nsIConsoleMessage*) + * mCurrentSize)); + + uint32_t i = 0; + for (MessageElement* e = mMessages.getFirst(); e != nullptr; e = e->getNext()) { + nsCOMPtr<nsIConsoleMessage> m = e->Get(); + m.forget(&messageArray[i]); + i++; + } + + MOZ_ASSERT(i == mCurrentSize); + + *aCount = i; + *aMessages = messageArray; + + return NS_OK; +} + +NS_IMETHODIMP +nsConsoleService::RegisterListener(nsIConsoleListener* aListener) +{ + if (!NS_IsMainThread()) { + NS_ERROR("nsConsoleService::RegisterListener is main thread only."); + return NS_ERROR_NOT_SAME_THREAD; + } + + nsCOMPtr<nsISupports> canonical = do_QueryInterface(aListener); + + MutexAutoLock lock(mLock); + if (mListeners.GetWeak(canonical)) { + // Reregistering a listener isn't good + return NS_ERROR_FAILURE; + } + mListeners.Put(canonical, aListener); + return NS_OK; +} + +NS_IMETHODIMP +nsConsoleService::UnregisterListener(nsIConsoleListener* aListener) +{ + if (!NS_IsMainThread()) { + NS_ERROR("nsConsoleService::UnregisterListener is main thread only."); + return NS_ERROR_NOT_SAME_THREAD; + } + + nsCOMPtr<nsISupports> canonical = do_QueryInterface(aListener); + + MutexAutoLock lock(mLock); + + if (!mListeners.GetWeak(canonical)) { + // Unregistering a listener that was never registered? + return NS_ERROR_FAILURE; + } + mListeners.Remove(canonical); + return NS_OK; +} + +NS_IMETHODIMP +nsConsoleService::Reset() +{ + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + + /* + * Make sure nobody trips into the buffer while it's being reset + */ + MutexAutoLock lock(mLock); + + ClearMessages(); + return NS_OK; +} + +NS_IMETHODIMP +nsConsoleService::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) +{ + if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { + // Dump all our messages, in case any are cycle collected. + Reset(); + // We could remove ourselves from the observer service, but it is about to + // drop all observers anyways, so why bother. + } else if (!strcmp(aTopic, "inner-window-destroyed")) { + nsCOMPtr<nsISupportsPRUint64> supportsInt = do_QueryInterface(aSubject); + MOZ_ASSERT(supportsInt); + + uint64_t windowId; + MOZ_ALWAYS_SUCCEEDS(supportsInt->GetData(&windowId)); + + ClearMessagesForWindowID(windowId); + } else { + MOZ_CRASH(); + } + return NS_OK; +} diff --git a/xpcom/base/nsConsoleService.h b/xpcom/base/nsConsoleService.h new file mode 100644 index 000000000..089de8106 --- /dev/null +++ b/xpcom/base/nsConsoleService.h @@ -0,0 +1,118 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * nsConsoleService class declaration. + */ + +#ifndef __nsconsoleservice_h__ +#define __nsconsoleservice_h__ + +#include "mozilla/Attributes.h" +#include "mozilla/Mutex.h" + +#include "nsInterfaceHashtable.h" +#include "nsHashKeys.h" + +#include "nsIConsoleService.h" + +class nsConsoleService final : public nsIConsoleService, + public nsIObserver +{ +public: + nsConsoleService(); + nsresult Init(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSICONSOLESERVICE + NS_DECL_NSIOBSERVER + + void SetIsDelivering() + { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!mDeliveringMessage); + mDeliveringMessage = true; + } + + void SetDoneDelivering() + { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mDeliveringMessage); + mDeliveringMessage = false; + } + + // This is a variant of LogMessage which allows the caller to determine + // if the message should be output to an OS-specific log. This is used on + // B2G to control whether the message is logged to the android log or not. + + enum OutputMode { + SuppressLog, + OutputToLog + }; + virtual nsresult LogMessageWithMode(nsIConsoleMessage* aMessage, + OutputMode aOutputMode); + + typedef nsInterfaceHashtable<nsISupportsHashKey, + nsIConsoleListener> ListenerHash; + void CollectCurrentListeners(nsCOMArray<nsIConsoleListener>& aListeners); + +private: + class MessageElement : public mozilla::LinkedListElement<MessageElement> + { + public: + explicit MessageElement(nsIConsoleMessage* aMessage) : mMessage(aMessage) + {} + + nsIConsoleMessage* Get() + { + return mMessage.get(); + } + + // Swap directly into an nsCOMPtr to avoid spurious refcount + // traffic off the main thread in debug builds from + // NSCAP_ASSERT_NO_QUERY_NEEDED(). + void swapMessage(nsCOMPtr<nsIConsoleMessage>& aRetVal) + { + mMessage.swap(aRetVal); + } + + ~MessageElement(); + + private: + nsCOMPtr<nsIConsoleMessage> mMessage; + + MessageElement(const MessageElement&) = delete; + MessageElement& operator=(const MessageElement&) = delete; + MessageElement(MessageElement&&) = delete; + MessageElement& operator=(MessageElement&&) = delete; + }; + + ~nsConsoleService(); + + void ClearMessagesForWindowID(const uint64_t innerID); + void ClearMessages(); + + mozilla::LinkedList<MessageElement> mMessages; + + // The current size of mMessages. + uint32_t mCurrentSize; + + // The maximum size of mMessages. + uint32_t mMaximumSize; + + // Are we currently delivering a console message on the main thread? If + // so, we suppress incoming messages on the main thread only, to avoid + // infinite repitition. + bool mDeliveringMessage; + + // Listeners to notify whenever a new message is logged. + ListenerHash mListeners; + + // To serialize interesting methods. + mozilla::Mutex mLock; +}; + +#endif /* __nsconsoleservice_h__ */ diff --git a/xpcom/base/nsCrashOnException.cpp b/xpcom/base/nsCrashOnException.cpp new file mode 100644 index 000000000..0f8042531 --- /dev/null +++ b/xpcom/base/nsCrashOnException.cpp @@ -0,0 +1,44 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsCrashOnException.h" +#include "nsCOMPtr.h" +#include "nsServiceManagerUtils.h" + +#ifdef MOZ_CRASHREPORTER +#include "nsICrashReporter.h" +#endif + +namespace mozilla { + +static int +ReportException(EXCEPTION_POINTERS* aExceptionInfo) +{ +#ifdef MOZ_CRASHREPORTER + nsCOMPtr<nsICrashReporter> cr = + do_GetService("@mozilla.org/toolkit/crash-reporter;1"); + if (cr) { + cr->WriteMinidumpForException(aExceptionInfo); + } +#endif + return EXCEPTION_EXECUTE_HANDLER; +} + +XPCOM_API(LRESULT) +CallWindowProcCrashProtected(WNDPROC aWndProc, HWND aHWnd, UINT aMsg, + WPARAM aWParam, LPARAM aLParam) +{ + MOZ_SEH_TRY { + return aWndProc(aHWnd, aMsg, aWParam, aLParam); + } + MOZ_SEH_EXCEPT(ReportException(GetExceptionInformation())) { + ::TerminateProcess(::GetCurrentProcess(), 253); + } + return 0; // not reached +} + +} + diff --git a/xpcom/base/nsCrashOnException.h b/xpcom/base/nsCrashOnException.h new file mode 100644 index 000000000..cdf3fdf09 --- /dev/null +++ b/xpcom/base/nsCrashOnException.h @@ -0,0 +1,23 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsCrashOnException_h +#define nsCrashOnException_h + +#include <nscore.h> +#include <windows.h> + +namespace mozilla { + +// Call a given window procedure, and catch any Win32 exceptions raised from it, +// and report them as crashes. +XPCOM_API(LRESULT) CallWindowProcCrashProtected(WNDPROC aWndProc, HWND aHWnd, + UINT aMsg, WPARAM aWParam, + LPARAM aLParam); + +} + +#endif diff --git a/xpcom/base/nsCycleCollector.cpp b/xpcom/base/nsCycleCollector.cpp new file mode 100644 index 000000000..ca7057628 --- /dev/null +++ b/xpcom/base/nsCycleCollector.cpp @@ -0,0 +1,4213 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// +// This file implements a garbage-cycle collector based on the paper +// +// Concurrent Cycle Collection in Reference Counted Systems +// Bacon & Rajan (2001), ECOOP 2001 / Springer LNCS vol 2072 +// +// We are not using the concurrent or acyclic cases of that paper; so +// the green, red and orange colors are not used. +// +// The collector is based on tracking pointers of four colors: +// +// Black nodes are definitely live. If we ever determine a node is +// black, it's ok to forget about, drop from our records. +// +// White nodes are definitely garbage cycles. Once we finish with our +// scanning, we unlink all the white nodes and expect that by +// unlinking them they will self-destruct (since a garbage cycle is +// only keeping itself alive with internal links, by definition). +// +// Snow-white is an addition to the original algorithm. Snow-white object +// has reference count zero and is just waiting for deletion. +// +// Grey nodes are being scanned. Nodes that turn grey will turn +// either black if we determine that they're live, or white if we +// determine that they're a garbage cycle. After the main collection +// algorithm there should be no grey nodes. +// +// Purple nodes are *candidates* for being scanned. They are nodes we +// haven't begun scanning yet because they're not old enough, or we're +// still partway through the algorithm. +// +// XPCOM objects participating in garbage-cycle collection are obliged +// to inform us when they ought to turn purple; that is, when their +// refcount transitions from N+1 -> N, for nonzero N. Furthermore we +// require that *after* an XPCOM object has informed us of turning +// purple, they will tell us when they either transition back to being +// black (incremented refcount) or are ultimately deleted. + +// Incremental cycle collection +// +// Beyond the simple state machine required to implement incremental +// collection, the CC needs to be able to compensate for things the browser +// is doing during the collection. There are two kinds of problems. For each +// of these, there are two cases to deal with: purple-buffered C++ objects +// and JS objects. + +// The first problem is that an object in the CC's graph can become garbage. +// This is bad because the CC touches the objects in its graph at every +// stage of its operation. +// +// All cycle collected C++ objects that die during a cycle collection +// will end up actually getting deleted by the SnowWhiteKiller. Before +// the SWK deletes an object, it checks if an ICC is running, and if so, +// if the object is in the graph. If it is, the CC clears mPointer and +// mParticipant so it does not point to the raw object any more. Because +// objects could die any time the CC returns to the mutator, any time the CC +// accesses a PtrInfo it must perform a null check on mParticipant to +// ensure the object has not gone away. +// +// JS objects don't always run finalizers, so the CC can't remove them from +// the graph when they die. Fortunately, JS objects can only die during a GC, +// so if a GC is begun during an ICC, the browser synchronously finishes off +// the ICC, which clears the entire CC graph. If the GC and CC are scheduled +// properly, this should be rare. +// +// The second problem is that objects in the graph can be changed, say by +// being addrefed or released, or by having a field updated, after the object +// has been added to the graph. The problem is that ICC can miss a newly +// created reference to an object, and end up unlinking an object that is +// actually alive. +// +// The basic idea of the solution, from "An on-the-fly Reference Counting +// Garbage Collector for Java" by Levanoni and Petrank, is to notice if an +// object has had an additional reference to it created during the collection, +// and if so, don't collect it during the current collection. This avoids having +// to rerun the scan as in Bacon & Rajan 2001. +// +// For cycle collected C++ objects, we modify AddRef to place the object in +// the purple buffer, in addition to Release. Then, in the CC, we treat any +// objects in the purple buffer as being alive, after graph building has +// completed. Because they are in the purple buffer, they will be suspected +// in the next CC, so there's no danger of leaks. This is imprecise, because +// we will treat as live an object that has been Released but not AddRefed +// during graph building, but that's probably rare enough that the additional +// bookkeeping overhead is not worthwhile. +// +// For JS objects, the cycle collector is only looking at gray objects. If a +// gray object is touched during ICC, it will be made black by UnmarkGray. +// Thus, if a JS object has become black during the ICC, we treat it as live. +// Merged JS zones have to be handled specially: we scan all zone globals. +// If any are black, we treat the zone as being black. + + +// Safety +// +// An XPCOM object is either scan-safe or scan-unsafe, purple-safe or +// purple-unsafe. +// +// An nsISupports object is scan-safe if: +// +// - It can be QI'ed to |nsXPCOMCycleCollectionParticipant|, though +// this operation loses ISupports identity (like nsIClassInfo). +// - Additionally, the operation |traverse| on the resulting +// nsXPCOMCycleCollectionParticipant does not cause *any* refcount +// adjustment to occur (no AddRef / Release calls). +// +// A non-nsISupports ("native") object is scan-safe by explicitly +// providing its nsCycleCollectionParticipant. +// +// An object is purple-safe if it satisfies the following properties: +// +// - The object is scan-safe. +// +// When we receive a pointer |ptr| via +// |nsCycleCollector::suspect(ptr)|, we assume it is purple-safe. We +// can check the scan-safety, but have no way to ensure the +// purple-safety; objects must obey, or else the entire system falls +// apart. Don't involve an object in this scheme if you can't +// guarantee its purple-safety. The easiest way to ensure that an +// object is purple-safe is to use nsCycleCollectingAutoRefCnt. +// +// When we have a scannable set of purple nodes ready, we begin +// our walks. During the walks, the nodes we |traverse| should only +// feed us more scan-safe nodes, and should not adjust the refcounts +// of those nodes. +// +// We do not |AddRef| or |Release| any objects during scanning. We +// rely on the purple-safety of the roots that call |suspect| to +// hold, such that we will clear the pointer from the purple buffer +// entry to the object before it is destroyed. The pointers that are +// merely scan-safe we hold only for the duration of scanning, and +// there should be no objects released from the scan-safe set during +// the scan. +// +// We *do* call |Root| and |Unroot| on every white object, on +// either side of the calls to |Unlink|. This keeps the set of white +// objects alive during the unlinking. +// + +#if !defined(__MINGW32__) +#ifdef WIN32 +#include <crtdbg.h> +#include <errno.h> +#endif +#endif + +#include "base/process_util.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/AutoRestore.h" +#include "mozilla/CycleCollectedJSContext.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/HoldDropJSObjects.h" +/* This must occur *after* base/process_util.h to avoid typedefs conflicts. */ +#include "mozilla/LinkedList.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/SegmentedVector.h" + +#include "nsCycleCollectionParticipant.h" +#include "nsCycleCollectionNoteRootCallback.h" +#include "nsDeque.h" +#include "nsCycleCollector.h" +#include "nsThreadUtils.h" +#include "nsXULAppAPI.h" +#include "prenv.h" +#include "nsPrintfCString.h" +#include "nsTArray.h" +#include "nsIConsoleService.h" +#include "mozilla/Attributes.h" +#include "nsICycleCollectorListener.h" +#include "nsIMemoryReporter.h" +#include "nsIFile.h" +#include "nsDumpUtils.h" +#include "xpcpublic.h" +#include "GeckoProfiler.h" +#include <stdint.h> +#include <stdio.h> + +#include "mozilla/AutoGlobalTimelineMarker.h" +#include "mozilla/Likely.h" +#include "mozilla/PoisonIOInterposer.h" +#include "mozilla/Telemetry.h" +#include "mozilla/ThreadLocal.h" + +#ifdef MOZ_CRASHREPORTER +#include "nsExceptionHandler.h" +#endif + +using namespace mozilla; + +//#define COLLECT_TIME_DEBUG + +// Enable assertions that are useful for diagnosing errors in graph construction. +//#define DEBUG_CC_GRAPH + +#define DEFAULT_SHUTDOWN_COLLECTIONS 5 + +// One to do the freeing, then another to detect there is no more work to do. +#define NORMAL_SHUTDOWN_COLLECTIONS 2 + +// Cycle collector environment variables +// +// MOZ_CC_LOG_ALL: If defined, always log cycle collector heaps. +// +// MOZ_CC_LOG_SHUTDOWN: If defined, log cycle collector heaps at shutdown. +// +// MOZ_CC_LOG_THREAD: If set to "main", only automatically log main thread +// CCs. If set to "worker", only automatically log worker CCs. If set to "all", +// log either. The default value is "all". This must be used with either +// MOZ_CC_LOG_ALL or MOZ_CC_LOG_SHUTDOWN for it to do anything. +// +// MOZ_CC_LOG_PROCESS: If set to "main", only automatically log main process +// CCs. If set to "content", only automatically log tab CCs. If set to +// "plugins", only automatically log plugin CCs. If set to "all", log +// everything. The default value is "all". This must be used with either +// MOZ_CC_LOG_ALL or MOZ_CC_LOG_SHUTDOWN for it to do anything. +// +// MOZ_CC_ALL_TRACES: If set to "all", any cycle collector +// logging done will be WantAllTraces, which disables +// various cycle collector optimizations to give a fuller picture of +// the heap. If set to "shutdown", only shutdown logging will be WantAllTraces. +// The default is none. +// +// MOZ_CC_RUN_DURING_SHUTDOWN: In non-DEBUG or builds, if this is set, +// run cycle collections at shutdown. +// +// MOZ_CC_LOG_DIRECTORY: The directory in which logs are placed (such as +// logs from MOZ_CC_LOG_ALL and MOZ_CC_LOG_SHUTDOWN, or other uses +// of nsICycleCollectorListener) + +// Various parameters of this collector can be tuned using environment +// variables. + +struct nsCycleCollectorParams +{ + bool mLogAll; + bool mLogShutdown; + bool mAllTracesAll; + bool mAllTracesShutdown; + bool mLogThisThread; + + nsCycleCollectorParams() : + mLogAll(PR_GetEnv("MOZ_CC_LOG_ALL") != nullptr), + mLogShutdown(PR_GetEnv("MOZ_CC_LOG_SHUTDOWN") != nullptr), + mAllTracesAll(false), + mAllTracesShutdown(false) + { + const char* logThreadEnv = PR_GetEnv("MOZ_CC_LOG_THREAD"); + bool threadLogging = true; + if (logThreadEnv && !!strcmp(logThreadEnv, "all")) { + if (NS_IsMainThread()) { + threadLogging = !strcmp(logThreadEnv, "main"); + } else { + threadLogging = !strcmp(logThreadEnv, "worker"); + } + } + + const char* logProcessEnv = PR_GetEnv("MOZ_CC_LOG_PROCESS"); + bool processLogging = true; + if (logProcessEnv && !!strcmp(logProcessEnv, "all")) { + switch (XRE_GetProcessType()) { + case GeckoProcessType_Default: + processLogging = !strcmp(logProcessEnv, "main"); + break; + case GeckoProcessType_Plugin: + processLogging = !strcmp(logProcessEnv, "plugins"); + break; + case GeckoProcessType_Content: + processLogging = !strcmp(logProcessEnv, "content"); + break; + default: + processLogging = false; + break; + } + } + mLogThisThread = threadLogging && processLogging; + + const char* allTracesEnv = PR_GetEnv("MOZ_CC_ALL_TRACES"); + if (allTracesEnv) { + if (!strcmp(allTracesEnv, "all")) { + mAllTracesAll = true; + } else if (!strcmp(allTracesEnv, "shutdown")) { + mAllTracesShutdown = true; + } + } + } + + bool LogThisCC(bool aIsShutdown) + { + return (mLogAll || (aIsShutdown && mLogShutdown)) && mLogThisThread; + } + + bool AllTracesThisCC(bool aIsShutdown) + { + return mAllTracesAll || (aIsShutdown && mAllTracesShutdown); + } +}; + +#ifdef COLLECT_TIME_DEBUG +class TimeLog +{ +public: + TimeLog() : mLastCheckpoint(TimeStamp::Now()) + { + } + + void + Checkpoint(const char* aEvent) + { + TimeStamp now = TimeStamp::Now(); + double dur = (now - mLastCheckpoint).ToMilliseconds(); + if (dur >= 0.5) { + printf("cc: %s took %.1fms\n", aEvent, dur); + } + mLastCheckpoint = now; + } + +private: + TimeStamp mLastCheckpoint; +}; +#else +class TimeLog +{ +public: + TimeLog() + { + } + void Checkpoint(const char* aEvent) + { + } +}; +#endif + + +//////////////////////////////////////////////////////////////////////// +// Base types +//////////////////////////////////////////////////////////////////////// + +struct PtrInfo; + +class EdgePool +{ +public: + // EdgePool allocates arrays of void*, primarily to hold PtrInfo*. + // However, at the end of a block, the last two pointers are a null + // and then a void** pointing to the next block. This allows + // EdgePool::Iterators to be a single word but still capable of crossing + // block boundaries. + + EdgePool() + { + mSentinelAndBlocks[0].block = nullptr; + mSentinelAndBlocks[1].block = nullptr; + } + + ~EdgePool() + { + MOZ_ASSERT(!mSentinelAndBlocks[0].block && + !mSentinelAndBlocks[1].block, + "Didn't call Clear()?"); + } + + void Clear() + { + EdgeBlock* b = EdgeBlocks(); + while (b) { + EdgeBlock* next = b->Next(); + delete b; + b = next; + } + + mSentinelAndBlocks[0].block = nullptr; + mSentinelAndBlocks[1].block = nullptr; + } + +#ifdef DEBUG + bool IsEmpty() + { + return !mSentinelAndBlocks[0].block && + !mSentinelAndBlocks[1].block; + } +#endif + +private: + struct EdgeBlock; + union PtrInfoOrBlock + { + // Use a union to avoid reinterpret_cast and the ensuing + // potential aliasing bugs. + PtrInfo* ptrInfo; + EdgeBlock* block; + }; + struct EdgeBlock + { + enum { EdgeBlockSize = 16 * 1024 }; + + PtrInfoOrBlock mPointers[EdgeBlockSize]; + EdgeBlock() + { + mPointers[EdgeBlockSize - 2].block = nullptr; // sentinel + mPointers[EdgeBlockSize - 1].block = nullptr; // next block pointer + } + EdgeBlock*& Next() + { + return mPointers[EdgeBlockSize - 1].block; + } + PtrInfoOrBlock* Start() + { + return &mPointers[0]; + } + PtrInfoOrBlock* End() + { + return &mPointers[EdgeBlockSize - 2]; + } + }; + + // Store the null sentinel so that we can have valid iterators + // before adding any edges and without adding any blocks. + PtrInfoOrBlock mSentinelAndBlocks[2]; + + EdgeBlock*& EdgeBlocks() + { + return mSentinelAndBlocks[1].block; + } + EdgeBlock* EdgeBlocks() const + { + return mSentinelAndBlocks[1].block; + } + +public: + class Iterator + { + public: + Iterator() : mPointer(nullptr) {} + explicit Iterator(PtrInfoOrBlock* aPointer) : mPointer(aPointer) {} + Iterator(const Iterator& aOther) : mPointer(aOther.mPointer) {} + + Iterator& operator++() + { + if (!mPointer->ptrInfo) { + // Null pointer is a sentinel for link to the next block. + mPointer = (mPointer + 1)->block->mPointers; + } + ++mPointer; + return *this; + } + + PtrInfo* operator*() const + { + if (!mPointer->ptrInfo) { + // Null pointer is a sentinel for link to the next block. + return (mPointer + 1)->block->mPointers->ptrInfo; + } + return mPointer->ptrInfo; + } + bool operator==(const Iterator& aOther) const + { + return mPointer == aOther.mPointer; + } + bool operator!=(const Iterator& aOther) const + { + return mPointer != aOther.mPointer; + } + +#ifdef DEBUG_CC_GRAPH + bool Initialized() const + { + return mPointer != nullptr; + } +#endif + + private: + PtrInfoOrBlock* mPointer; + }; + + class Builder; + friend class Builder; + class Builder + { + public: + explicit Builder(EdgePool& aPool) + : mCurrent(&aPool.mSentinelAndBlocks[0]) + , mBlockEnd(&aPool.mSentinelAndBlocks[0]) + , mNextBlockPtr(&aPool.EdgeBlocks()) + { + } + + Iterator Mark() + { + return Iterator(mCurrent); + } + + void Add(PtrInfo* aEdge) + { + if (mCurrent == mBlockEnd) { + EdgeBlock* b = new EdgeBlock(); + *mNextBlockPtr = b; + mCurrent = b->Start(); + mBlockEnd = b->End(); + mNextBlockPtr = &b->Next(); + } + (mCurrent++)->ptrInfo = aEdge; + } + private: + // mBlockEnd points to space for null sentinel + PtrInfoOrBlock* mCurrent; + PtrInfoOrBlock* mBlockEnd; + EdgeBlock** mNextBlockPtr; + }; + + size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const + { + size_t n = 0; + EdgeBlock* b = EdgeBlocks(); + while (b) { + n += aMallocSizeOf(b); + b = b->Next(); + } + return n; + } +}; + +#ifdef DEBUG_CC_GRAPH +#define CC_GRAPH_ASSERT(b) MOZ_ASSERT(b) +#else +#define CC_GRAPH_ASSERT(b) +#endif + +#define CC_TELEMETRY(_name, _value) \ + PR_BEGIN_MACRO \ + if (NS_IsMainThread()) { \ + Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR##_name, _value); \ + } else { \ + Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_WORKER##_name, _value); \ + } \ + PR_END_MACRO + +enum NodeColor { black, white, grey }; + +// This structure should be kept as small as possible; we may expect +// hundreds of thousands of them to be allocated and touched +// repeatedly during each cycle collection. + +struct PtrInfo +{ + void* mPointer; + nsCycleCollectionParticipant* mParticipant; + uint32_t mColor : 2; + uint32_t mInternalRefs : 30; + uint32_t mRefCount; +private: + EdgePool::Iterator mFirstChild; + + static const uint32_t kInitialRefCount = UINT32_MAX - 1; + +public: + + PtrInfo(void* aPointer, nsCycleCollectionParticipant* aParticipant) + : mPointer(aPointer), + mParticipant(aParticipant), + mColor(grey), + mInternalRefs(0), + mRefCount(kInitialRefCount), + mFirstChild() + { + MOZ_ASSERT(aParticipant); + + // We initialize mRefCount to a large non-zero value so + // that it doesn't look like a JS object to the cycle collector + // in the case where the object dies before being traversed. + MOZ_ASSERT(!IsGrayJS() && !IsBlackJS()); + } + + // Allow NodePool::NodeBlock's constructor to compile. + PtrInfo() + { + NS_NOTREACHED("should never be called"); + } + + bool IsGrayJS() const + { + return mRefCount == 0; + } + + bool IsBlackJS() const + { + return mRefCount == UINT32_MAX; + } + + bool WasTraversed() const + { + return mRefCount != kInitialRefCount; + } + + EdgePool::Iterator FirstChild() const + { + CC_GRAPH_ASSERT(mFirstChild.Initialized()); + return mFirstChild; + } + + // this PtrInfo must be part of a NodePool + EdgePool::Iterator LastChild() const + { + CC_GRAPH_ASSERT((this + 1)->mFirstChild.Initialized()); + return (this + 1)->mFirstChild; + } + + void SetFirstChild(EdgePool::Iterator aFirstChild) + { + CC_GRAPH_ASSERT(aFirstChild.Initialized()); + mFirstChild = aFirstChild; + } + + // this PtrInfo must be part of a NodePool + void SetLastChild(EdgePool::Iterator aLastChild) + { + CC_GRAPH_ASSERT(aLastChild.Initialized()); + (this + 1)->mFirstChild = aLastChild; + } +}; + +/** + * A structure designed to be used like a linked list of PtrInfo, except + * it allocates many PtrInfos at a time. + */ +class NodePool +{ +private: + // The -2 allows us to use |NodeBlockSize + 1| for |mEntries|, and fit + // |mNext|, all without causing slop. + enum { NodeBlockSize = 4 * 1024 - 2 }; + + struct NodeBlock + { + // We create and destroy NodeBlock using moz_xmalloc/free rather than new + // and delete to avoid calling its constructor and destructor. + NodeBlock() + { + NS_NOTREACHED("should never be called"); + + // Ensure NodeBlock is the right size (see the comment on NodeBlockSize + // above). + static_assert( + sizeof(NodeBlock) == 81904 || // 32-bit; equals 19.996 x 4 KiB pages + sizeof(NodeBlock) == 131048, // 64-bit; equals 31.994 x 4 KiB pages + "ill-sized NodeBlock" + ); + } + ~NodeBlock() + { + NS_NOTREACHED("should never be called"); + } + + NodeBlock* mNext; + PtrInfo mEntries[NodeBlockSize + 1]; // +1 to store last child of last node + }; + +public: + NodePool() + : mBlocks(nullptr) + , mLast(nullptr) + { + } + + ~NodePool() + { + MOZ_ASSERT(!mBlocks, "Didn't call Clear()?"); + } + + void Clear() + { + NodeBlock* b = mBlocks; + while (b) { + NodeBlock* n = b->mNext; + free(b); + b = n; + } + + mBlocks = nullptr; + mLast = nullptr; + } + +#ifdef DEBUG + bool IsEmpty() + { + return !mBlocks && !mLast; + } +#endif + + class Builder; + friend class Builder; + class Builder + { + public: + explicit Builder(NodePool& aPool) + : mNextBlock(&aPool.mBlocks) + , mNext(aPool.mLast) + , mBlockEnd(nullptr) + { + MOZ_ASSERT(!aPool.mBlocks && !aPool.mLast, "pool not empty"); + } + PtrInfo* Add(void* aPointer, nsCycleCollectionParticipant* aParticipant) + { + if (mNext == mBlockEnd) { + NodeBlock* block = static_cast<NodeBlock*>(malloc(sizeof(NodeBlock))); + if (!block) { + return nullptr; + } + + *mNextBlock = block; + mNext = block->mEntries; + mBlockEnd = block->mEntries + NodeBlockSize; + block->mNext = nullptr; + mNextBlock = &block->mNext; + } + return new (mozilla::KnownNotNull, mNext++) PtrInfo(aPointer, aParticipant); + } + private: + NodeBlock** mNextBlock; + PtrInfo*& mNext; + PtrInfo* mBlockEnd; + }; + + class Enumerator; + friend class Enumerator; + class Enumerator + { + public: + explicit Enumerator(NodePool& aPool) + : mFirstBlock(aPool.mBlocks) + , mCurBlock(nullptr) + , mNext(nullptr) + , mBlockEnd(nullptr) + , mLast(aPool.mLast) + { + } + + bool IsDone() const + { + return mNext == mLast; + } + + bool AtBlockEnd() const + { + return mNext == mBlockEnd; + } + + PtrInfo* GetNext() + { + MOZ_ASSERT(!IsDone(), "calling GetNext when done"); + if (mNext == mBlockEnd) { + NodeBlock* nextBlock = mCurBlock ? mCurBlock->mNext : mFirstBlock; + mNext = nextBlock->mEntries; + mBlockEnd = mNext + NodeBlockSize; + mCurBlock = nextBlock; + } + return mNext++; + } + private: + // mFirstBlock is a reference to allow an Enumerator to be constructed + // for an empty graph. + NodeBlock*& mFirstBlock; + NodeBlock* mCurBlock; + // mNext is the next value we want to return, unless mNext == mBlockEnd + // NB: mLast is a reference to allow enumerating while building! + PtrInfo* mNext; + PtrInfo* mBlockEnd; + PtrInfo*& mLast; + }; + + size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const + { + // We don't measure the things pointed to by mEntries[] because those + // pointers are non-owning. + size_t n = 0; + NodeBlock* b = mBlocks; + while (b) { + n += aMallocSizeOf(b); + b = b->mNext; + } + return n; + } + +private: + NodeBlock* mBlocks; + PtrInfo* mLast; +}; + + +// Declarations for mPtrToNodeMap. + +struct PtrToNodeEntry : public PLDHashEntryHdr +{ + // The key is mNode->mPointer + PtrInfo* mNode; +}; + +static bool +PtrToNodeMatchEntry(const PLDHashEntryHdr* aEntry, const void* aKey) +{ + const PtrToNodeEntry* n = static_cast<const PtrToNodeEntry*>(aEntry); + return n->mNode->mPointer == aKey; +} + +static PLDHashTableOps PtrNodeOps = { + PLDHashTable::HashVoidPtrKeyStub, + PtrToNodeMatchEntry, + PLDHashTable::MoveEntryStub, + PLDHashTable::ClearEntryStub, + nullptr +}; + + +struct WeakMapping +{ + // map and key will be null if the corresponding objects are GC marked + PtrInfo* mMap; + PtrInfo* mKey; + PtrInfo* mKeyDelegate; + PtrInfo* mVal; +}; + +class CCGraphBuilder; + +struct CCGraph +{ + NodePool mNodes; + EdgePool mEdges; + nsTArray<WeakMapping> mWeakMaps; + uint32_t mRootCount; + +private: + PLDHashTable mPtrToNodeMap; + bool mOutOfMemory; + + static const uint32_t kInitialMapLength = 16384; + +public: + CCGraph() + : mRootCount(0) + , mPtrToNodeMap(&PtrNodeOps, sizeof(PtrToNodeEntry), kInitialMapLength) + , mOutOfMemory(false) + {} + + ~CCGraph() {} + + void Init() + { + MOZ_ASSERT(IsEmpty(), "Failed to call CCGraph::Clear"); + } + + void Clear() + { + mNodes.Clear(); + mEdges.Clear(); + mWeakMaps.Clear(); + mRootCount = 0; + mPtrToNodeMap.ClearAndPrepareForLength(kInitialMapLength); + mOutOfMemory = false; + } + +#ifdef DEBUG + bool IsEmpty() + { + return mNodes.IsEmpty() && mEdges.IsEmpty() && + mWeakMaps.IsEmpty() && mRootCount == 0 && + mPtrToNodeMap.EntryCount() == 0; + } +#endif + + PtrInfo* FindNode(void* aPtr); + PtrToNodeEntry* AddNodeToMap(void* aPtr); + void RemoveObjectFromMap(void* aObject); + + uint32_t MapCount() const + { + return mPtrToNodeMap.EntryCount(); + } + + size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const + { + size_t n = 0; + + n += mNodes.SizeOfExcludingThis(aMallocSizeOf); + n += mEdges.SizeOfExcludingThis(aMallocSizeOf); + + // We don't measure what the WeakMappings point to, because the + // pointers are non-owning. + n += mWeakMaps.ShallowSizeOfExcludingThis(aMallocSizeOf); + + n += mPtrToNodeMap.ShallowSizeOfExcludingThis(aMallocSizeOf); + + return n; + } + +private: + PtrToNodeEntry* FindNodeEntry(void* aPtr) + { + return static_cast<PtrToNodeEntry*>(mPtrToNodeMap.Search(aPtr)); + } +}; + +PtrInfo* +CCGraph::FindNode(void* aPtr) +{ + PtrToNodeEntry* e = FindNodeEntry(aPtr); + return e ? e->mNode : nullptr; +} + +PtrToNodeEntry* +CCGraph::AddNodeToMap(void* aPtr) +{ + JS::AutoSuppressGCAnalysis suppress; + if (mOutOfMemory) { + return nullptr; + } + + auto e = static_cast<PtrToNodeEntry*>(mPtrToNodeMap.Add(aPtr, fallible)); + if (!e) { + mOutOfMemory = true; + MOZ_ASSERT(false, "Ran out of memory while building cycle collector graph"); + return nullptr; + } + return e; +} + +void +CCGraph::RemoveObjectFromMap(void* aObj) +{ + PtrToNodeEntry* e = FindNodeEntry(aObj); + PtrInfo* pinfo = e ? e->mNode : nullptr; + if (pinfo) { + mPtrToNodeMap.RemoveEntry(e); + + pinfo->mPointer = nullptr; + pinfo->mParticipant = nullptr; + } +} + + +static nsISupports* +CanonicalizeXPCOMParticipant(nsISupports* aIn) +{ + nsISupports* out = nullptr; + aIn->QueryInterface(NS_GET_IID(nsCycleCollectionISupports), + reinterpret_cast<void**>(&out)); + return out; +} + +static inline void +ToParticipant(nsISupports* aPtr, nsXPCOMCycleCollectionParticipant** aCp); + +static void +CanonicalizeParticipant(void** aParti, nsCycleCollectionParticipant** aCp) +{ + // If the participant is null, this is an nsISupports participant, + // so we must QI to get the real participant. + + if (!*aCp) { + nsISupports* nsparti = static_cast<nsISupports*>(*aParti); + nsparti = CanonicalizeXPCOMParticipant(nsparti); + NS_ASSERTION(nsparti, + "Don't add objects that don't participate in collection!"); + nsXPCOMCycleCollectionParticipant* xcp; + ToParticipant(nsparti, &xcp); + *aParti = nsparti; + *aCp = xcp; + } +} + +struct nsPurpleBufferEntry +{ + union + { + void* mObject; // when low bit unset + nsPurpleBufferEntry* mNextInFreeList; // when low bit set + }; + + nsCycleCollectingAutoRefCnt* mRefCnt; + + nsCycleCollectionParticipant* mParticipant; // nullptr for nsISupports +}; + +class nsCycleCollector; + +struct nsPurpleBuffer +{ +private: + struct PurpleBlock + { + PurpleBlock* mNext; + // Try to match the size of a jemalloc bucket, to minimize slop bytes. + // - On 32-bit platforms sizeof(nsPurpleBufferEntry) is 12, so mEntries + // is 16,380 bytes, which leaves 4 bytes for mNext. + // - On 64-bit platforms sizeof(nsPurpleBufferEntry) is 24, so mEntries + // is 32,544 bytes, which leaves 8 bytes for mNext. + nsPurpleBufferEntry mEntries[1365]; + + PurpleBlock() : mNext(nullptr) + { + // Ensure PurpleBlock is the right size (see above). + static_assert( + sizeof(PurpleBlock) == 16384 || // 32-bit + sizeof(PurpleBlock) == 32768, // 64-bit + "ill-sized nsPurpleBuffer::PurpleBlock" + ); + + InitNextPointers(); + } + + // Put all the entries in the block on the free list. + void InitNextPointers() + { + for (uint32_t i = 1; i < ArrayLength(mEntries); ++i) { + mEntries[i - 1].mNextInFreeList = + (nsPurpleBufferEntry*)(uintptr_t(mEntries + i) | 1); + } + mEntries[ArrayLength(mEntries) - 1].mNextInFreeList = + (nsPurpleBufferEntry*)1; + } + + template<class PurpleVisitor> + void VisitEntries(nsPurpleBuffer& aBuffer, PurpleVisitor& aVisitor) + { + nsPurpleBufferEntry* eEnd = ArrayEnd(mEntries); + for (nsPurpleBufferEntry* e = mEntries; e != eEnd; ++e) { + MOZ_ASSERT(e->mObject, "There should be no null mObject when we iterate over the purple buffer"); + if (!(uintptr_t(e->mObject) & uintptr_t(1)) && e->mObject) { + aVisitor.Visit(aBuffer, e); + } + } + } + }; + // This class wraps a linked list of the elements in the purple + // buffer. + + uint32_t mCount; + PurpleBlock mFirstBlock; + nsPurpleBufferEntry* mFreeList; + +public: + nsPurpleBuffer() + { + InitBlocks(); + } + + ~nsPurpleBuffer() + { + FreeBlocks(); + } + + template<class PurpleVisitor> + void VisitEntries(PurpleVisitor& aVisitor) + { + for (PurpleBlock* b = &mFirstBlock; b; b = b->mNext) { + b->VisitEntries(*this, aVisitor); + } + } + + void InitBlocks() + { + mCount = 0; + mFreeList = mFirstBlock.mEntries; + } + + void FreeBlocks() + { + if (mCount > 0) { + UnmarkRemainingPurple(&mFirstBlock); + } + PurpleBlock* b = mFirstBlock.mNext; + while (b) { + if (mCount > 0) { + UnmarkRemainingPurple(b); + } + PurpleBlock* next = b->mNext; + delete b; + b = next; + } + mFirstBlock.mNext = nullptr; + } + + struct UnmarkRemainingPurpleVisitor + { + void + Visit(nsPurpleBuffer& aBuffer, nsPurpleBufferEntry* aEntry) + { + if (aEntry->mRefCnt) { + aEntry->mRefCnt->RemoveFromPurpleBuffer(); + aEntry->mRefCnt = nullptr; + } + aEntry->mObject = nullptr; + --aBuffer.mCount; + } + }; + + void UnmarkRemainingPurple(PurpleBlock* aBlock) + { + UnmarkRemainingPurpleVisitor visitor; + aBlock->VisitEntries(*this, visitor); + } + + void SelectPointers(CCGraphBuilder& aBuilder); + + // RemoveSkippable removes entries from the purple buffer synchronously + // (1) if aAsyncSnowWhiteFreeing is false and nsPurpleBufferEntry::mRefCnt is 0 or + // (2) if the object's nsXPCOMCycleCollectionParticipant::CanSkip() returns true or + // (3) if nsPurpleBufferEntry::mRefCnt->IsPurple() is false. + // (4) If removeChildlessNodes is true, then any nodes in the purple buffer + // that will have no children in the cycle collector graph will also be + // removed. CanSkip() may be run on these children. + void RemoveSkippable(nsCycleCollector* aCollector, + bool aRemoveChildlessNodes, + bool aAsyncSnowWhiteFreeing, + CC_ForgetSkippableCallback aCb); + + MOZ_ALWAYS_INLINE nsPurpleBufferEntry* NewEntry() + { + if (MOZ_UNLIKELY(!mFreeList)) { + PurpleBlock* b = new PurpleBlock; + mFreeList = b->mEntries; + + // Add the new block as the second block in the list. + b->mNext = mFirstBlock.mNext; + mFirstBlock.mNext = b; + } + + nsPurpleBufferEntry* e = mFreeList; + mFreeList = (nsPurpleBufferEntry*) + (uintptr_t(mFreeList->mNextInFreeList) & ~uintptr_t(1)); + return e; + } + + MOZ_ALWAYS_INLINE void Put(void* aObject, nsCycleCollectionParticipant* aCp, + nsCycleCollectingAutoRefCnt* aRefCnt) + { + nsPurpleBufferEntry* e = NewEntry(); + + ++mCount; + + e->mObject = aObject; + e->mRefCnt = aRefCnt; + e->mParticipant = aCp; + } + + void Remove(nsPurpleBufferEntry* aEntry) + { + MOZ_ASSERT(mCount != 0, "must have entries"); + + if (aEntry->mRefCnt) { + aEntry->mRefCnt->RemoveFromPurpleBuffer(); + aEntry->mRefCnt = nullptr; + } + aEntry->mNextInFreeList = + (nsPurpleBufferEntry*)(uintptr_t(mFreeList) | uintptr_t(1)); + mFreeList = aEntry; + + --mCount; + } + + uint32_t Count() const + { + return mCount; + } + + size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const + { + size_t n = 0; + + // Don't measure mFirstBlock because it's within |this|. + const PurpleBlock* block = mFirstBlock.mNext; + while (block) { + n += aMallocSizeOf(block); + block = block->mNext; + } + + // mFreeList is deliberately not measured because it points into + // the purple buffer, which is within mFirstBlock and thus within |this|. + // + // We also don't measure the things pointed to by mEntries[] because + // those pointers are non-owning. + + return n; + } +}; + +static bool +AddPurpleRoot(CCGraphBuilder& aBuilder, void* aRoot, + nsCycleCollectionParticipant* aParti); + +struct SelectPointersVisitor +{ + explicit SelectPointersVisitor(CCGraphBuilder& aBuilder) + : mBuilder(aBuilder) + { + } + + void + Visit(nsPurpleBuffer& aBuffer, nsPurpleBufferEntry* aEntry) + { + MOZ_ASSERT(aEntry->mObject, "Null object in purple buffer"); + MOZ_ASSERT(aEntry->mRefCnt->get() != 0, + "SelectPointersVisitor: snow-white object in the purple buffer"); + if (!aEntry->mRefCnt->IsPurple() || + AddPurpleRoot(mBuilder, aEntry->mObject, aEntry->mParticipant)) { + aBuffer.Remove(aEntry); + } + } + +private: + CCGraphBuilder& mBuilder; +}; + +void +nsPurpleBuffer::SelectPointers(CCGraphBuilder& aBuilder) +{ + SelectPointersVisitor visitor(aBuilder); + VisitEntries(visitor); + + NS_ASSERTION(mCount == 0, "AddPurpleRoot failed"); + if (mCount == 0) { + FreeBlocks(); + InitBlocks(); + mFirstBlock.InitNextPointers(); + } +} + +enum ccPhase +{ + IdlePhase, + GraphBuildingPhase, + ScanAndCollectWhitePhase, + CleanupPhase +}; + +enum ccType +{ + SliceCC, /* If a CC is in progress, continue it. Otherwise, start a new one. */ + ManualCC, /* Explicitly triggered. */ + ShutdownCC /* Shutdown CC, used for finding leaks. */ +}; + +//////////////////////////////////////////////////////////////////////// +// Top level structure for the cycle collector. +//////////////////////////////////////////////////////////////////////// + +using js::SliceBudget; + +class JSPurpleBuffer; + +class nsCycleCollector : public nsIMemoryReporter +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIMEMORYREPORTER + +private: + bool mActivelyCollecting; + bool mFreeingSnowWhite; + // mScanInProgress should be false when we're collecting white objects. + bool mScanInProgress; + CycleCollectorResults mResults; + TimeStamp mCollectionStart; + + CycleCollectedJSContext* mJSContext; + + ccPhase mIncrementalPhase; + CCGraph mGraph; + nsAutoPtr<CCGraphBuilder> mBuilder; + RefPtr<nsCycleCollectorLogger> mLogger; + +#ifdef DEBUG + void* mThread; +#endif + + nsCycleCollectorParams mParams; + + uint32_t mWhiteNodeCount; + + CC_BeforeUnlinkCallback mBeforeUnlinkCB; + CC_ForgetSkippableCallback mForgetSkippableCB; + + nsPurpleBuffer mPurpleBuf; + + uint32_t mUnmergedNeeded; + uint32_t mMergedInARow; + + RefPtr<JSPurpleBuffer> mJSPurpleBuffer; + +private: + virtual ~nsCycleCollector(); + +public: + nsCycleCollector(); + + void RegisterJSContext(CycleCollectedJSContext* aJSContext); + void ForgetJSContext(); + + void SetBeforeUnlinkCallback(CC_BeforeUnlinkCallback aBeforeUnlinkCB) + { + CheckThreadSafety(); + mBeforeUnlinkCB = aBeforeUnlinkCB; + } + + void SetForgetSkippableCallback(CC_ForgetSkippableCallback aForgetSkippableCB) + { + CheckThreadSafety(); + mForgetSkippableCB = aForgetSkippableCB; + } + + void Suspect(void* aPtr, nsCycleCollectionParticipant* aCp, + nsCycleCollectingAutoRefCnt* aRefCnt); + uint32_t SuspectedCount(); + void ForgetSkippable(bool aRemoveChildlessNodes, bool aAsyncSnowWhiteFreeing); + bool FreeSnowWhite(bool aUntilNoSWInPurpleBuffer); + + // This method assumes its argument is already canonicalized. + void RemoveObjectFromGraph(void* aPtr); + + void PrepareForGarbageCollection(); + void FinishAnyCurrentCollection(); + + bool Collect(ccType aCCType, + SliceBudget& aBudget, + nsICycleCollectorListener* aManualListener, + bool aPreferShorterSlices = false); + void Shutdown(bool aDoCollect); + + bool IsIdle() const { return mIncrementalPhase == IdlePhase; } + + void SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf, + size_t* aObjectSize, + size_t* aGraphSize, + size_t* aPurpleBufferSize) const; + + JSPurpleBuffer* GetJSPurpleBuffer(); + + CycleCollectedJSContext* Context() { return mJSContext; } + +private: + void CheckThreadSafety(); + void ShutdownCollect(); + + void FixGrayBits(bool aForceGC, TimeLog& aTimeLog); + bool IsIncrementalGCInProgress(); + void FinishAnyIncrementalGCInProgress(); + bool ShouldMergeZones(ccType aCCType); + + void BeginCollection(ccType aCCType, nsICycleCollectorListener* aManualListener); + void MarkRoots(SliceBudget& aBudget); + void ScanRoots(bool aFullySynchGraphBuild); + void ScanIncrementalRoots(); + void ScanWhiteNodes(bool aFullySynchGraphBuild); + void ScanBlackNodes(); + void ScanWeakMaps(); + + // returns whether anything was collected + bool CollectWhite(); + + void CleanupAfterCollection(); +}; + +NS_IMPL_ISUPPORTS(nsCycleCollector, nsIMemoryReporter) + +/** + * GraphWalker is templatized over a Visitor class that must provide + * the following two methods: + * + * bool ShouldVisitNode(PtrInfo const *pi); + * void VisitNode(PtrInfo *pi); + */ +template<class Visitor> +class GraphWalker +{ +private: + Visitor mVisitor; + + void DoWalk(nsDeque& aQueue); + + void CheckedPush(nsDeque& aQueue, PtrInfo* aPi) + { + if (!aPi) { + MOZ_CRASH(); + } + if (!aQueue.Push(aPi, fallible)) { + mVisitor.Failed(); + } + } + +public: + void Walk(PtrInfo* aPi); + void WalkFromRoots(CCGraph& aGraph); + // copy-constructing the visitor should be cheap, and less + // indirection than using a reference + explicit GraphWalker(const Visitor aVisitor) : mVisitor(aVisitor) + { + } +}; + + +//////////////////////////////////////////////////////////////////////// +// The static collector struct +//////////////////////////////////////////////////////////////////////// + +struct CollectorData +{ + RefPtr<nsCycleCollector> mCollector; + CycleCollectedJSContext* mContext; +}; + +static MOZ_THREAD_LOCAL(CollectorData*) sCollectorData; + +//////////////////////////////////////////////////////////////////////// +// Utility functions +//////////////////////////////////////////////////////////////////////// + +static inline void +ToParticipant(nsISupports* aPtr, nsXPCOMCycleCollectionParticipant** aCp) +{ + // We use QI to move from an nsISupports to an + // nsXPCOMCycleCollectionParticipant, which is a per-class singleton helper + // object that implements traversal and unlinking logic for the nsISupports + // in question. + *aCp = nullptr; + CallQueryInterface(aPtr, aCp); +} + +template<class Visitor> +MOZ_NEVER_INLINE void +GraphWalker<Visitor>::Walk(PtrInfo* aPi) +{ + nsDeque queue; + CheckedPush(queue, aPi); + DoWalk(queue); +} + +template<class Visitor> +MOZ_NEVER_INLINE void +GraphWalker<Visitor>::WalkFromRoots(CCGraph& aGraph) +{ + nsDeque queue; + NodePool::Enumerator etor(aGraph.mNodes); + for (uint32_t i = 0; i < aGraph.mRootCount; ++i) { + CheckedPush(queue, etor.GetNext()); + } + DoWalk(queue); +} + +template<class Visitor> +MOZ_NEVER_INLINE void +GraphWalker<Visitor>::DoWalk(nsDeque& aQueue) +{ + // Use a aQueue to match the breadth-first traversal used when we + // built the graph, for hopefully-better locality. + while (aQueue.GetSize() > 0) { + PtrInfo* pi = static_cast<PtrInfo*>(aQueue.PopFront()); + + if (pi->WasTraversed() && mVisitor.ShouldVisitNode(pi)) { + mVisitor.VisitNode(pi); + for (EdgePool::Iterator child = pi->FirstChild(), + child_end = pi->LastChild(); + child != child_end; ++child) { + CheckedPush(aQueue, *child); + } + } + } +} + +struct CCGraphDescriber : public LinkedListElement<CCGraphDescriber> +{ + CCGraphDescriber() + : mAddress("0x"), mCnt(0), mType(eUnknown) + { + } + + enum Type + { + eRefCountedObject, + eGCedObject, + eGCMarkedObject, + eEdge, + eRoot, + eGarbage, + eUnknown + }; + + nsCString mAddress; + nsCString mName; + nsCString mCompartmentOrToAddress; + uint32_t mCnt; + Type mType; +}; + +class LogStringMessageAsync : public CancelableRunnable +{ +public: + explicit LogStringMessageAsync(const nsAString& aMsg) : mMsg(aMsg) + {} + + NS_IMETHOD Run() override + { + nsCOMPtr<nsIConsoleService> cs = + do_GetService(NS_CONSOLESERVICE_CONTRACTID); + if (cs) { + cs->LogStringMessage(mMsg.get()); + } + return NS_OK; + } + +private: + nsString mMsg; +}; + +class nsCycleCollectorLogSinkToFile final : public nsICycleCollectorLogSink +{ +public: + NS_DECL_ISUPPORTS + + nsCycleCollectorLogSinkToFile() : + mProcessIdentifier(base::GetCurrentProcId()), + mGCLog("gc-edges"), mCCLog("cc-edges") + { + } + + NS_IMETHOD GetFilenameIdentifier(nsAString& aIdentifier) override + { + aIdentifier = mFilenameIdentifier; + return NS_OK; + } + + NS_IMETHOD SetFilenameIdentifier(const nsAString& aIdentifier) override + { + mFilenameIdentifier = aIdentifier; + return NS_OK; + } + + NS_IMETHOD GetProcessIdentifier(int32_t* aIdentifier) override + { + *aIdentifier = mProcessIdentifier; + return NS_OK; + } + + NS_IMETHOD SetProcessIdentifier(int32_t aIdentifier) override + { + mProcessIdentifier = aIdentifier; + return NS_OK; + } + + NS_IMETHOD GetGcLog(nsIFile** aPath) override + { + NS_IF_ADDREF(*aPath = mGCLog.mFile); + return NS_OK; + } + + NS_IMETHOD GetCcLog(nsIFile** aPath) override + { + NS_IF_ADDREF(*aPath = mCCLog.mFile); + return NS_OK; + } + + NS_IMETHOD Open(FILE** aGCLog, FILE** aCCLog) override + { + nsresult rv; + + if (mGCLog.mStream || mCCLog.mStream) { + return NS_ERROR_UNEXPECTED; + } + + rv = OpenLog(&mGCLog); + NS_ENSURE_SUCCESS(rv, rv); + *aGCLog = mGCLog.mStream; + + rv = OpenLog(&mCCLog); + NS_ENSURE_SUCCESS(rv, rv); + *aCCLog = mCCLog.mStream; + + return NS_OK; + } + + NS_IMETHOD CloseGCLog() override + { + if (!mGCLog.mStream) { + return NS_ERROR_UNEXPECTED; + } + CloseLog(&mGCLog, NS_LITERAL_STRING("Garbage")); + return NS_OK; + } + + NS_IMETHOD CloseCCLog() override + { + if (!mCCLog.mStream) { + return NS_ERROR_UNEXPECTED; + } + CloseLog(&mCCLog, NS_LITERAL_STRING("Cycle")); + return NS_OK; + } + +private: + ~nsCycleCollectorLogSinkToFile() + { + if (mGCLog.mStream) { + MozillaUnRegisterDebugFILE(mGCLog.mStream); + fclose(mGCLog.mStream); + } + if (mCCLog.mStream) { + MozillaUnRegisterDebugFILE(mCCLog.mStream); + fclose(mCCLog.mStream); + } + } + + struct FileInfo + { + const char* const mPrefix; + nsCOMPtr<nsIFile> mFile; + FILE* mStream; + + explicit FileInfo(const char* aPrefix) : mPrefix(aPrefix), mStream(nullptr) { } + }; + + /** + * Create a new file named something like aPrefix.$PID.$IDENTIFIER.log in + * $MOZ_CC_LOG_DIRECTORY or in the system's temp directory. No existing + * file will be overwritten; if aPrefix.$PID.$IDENTIFIER.log exists, we'll + * try a file named something like aPrefix.$PID.$IDENTIFIER-1.log, and so + * on. + */ + already_AddRefed<nsIFile> CreateTempFile(const char* aPrefix) + { + nsPrintfCString filename("%s.%d%s%s.log", + aPrefix, + mProcessIdentifier, + mFilenameIdentifier.IsEmpty() ? "" : ".", + NS_ConvertUTF16toUTF8(mFilenameIdentifier).get()); + + // Get the log directory either from $MOZ_CC_LOG_DIRECTORY or from + // the fallback directories in OpenTempFile. We don't use an nsCOMPtr + // here because OpenTempFile uses an in/out param and getter_AddRefs + // wouldn't work. + nsIFile* logFile = nullptr; + if (char* env = PR_GetEnv("MOZ_CC_LOG_DIRECTORY")) { + NS_NewNativeLocalFile(nsCString(env), /* followLinks = */ true, + &logFile); + } + + // On Android or B2G, this function will open a file named + // aFilename under a memory-reporting-specific folder + // (/data/local/tmp/memory-reports). Otherwise, it will open a + // file named aFilename under "NS_OS_TEMP_DIR". + nsresult rv = nsDumpUtils::OpenTempFile(filename, &logFile, + NS_LITERAL_CSTRING("memory-reports")); + if (NS_FAILED(rv)) { + NS_IF_RELEASE(logFile); + return nullptr; + } + + return dont_AddRef(logFile); + } + + nsresult OpenLog(FileInfo* aLog) + { + // Initially create the log in a file starting with "incomplete-". + // We'll move the file and strip off the "incomplete-" once the dump + // completes. (We do this because we don't want scripts which poll + // the filesystem looking for GC/CC dumps to grab a file before we're + // finished writing to it.) + nsAutoCString incomplete; + incomplete += "incomplete-"; + incomplete += aLog->mPrefix; + MOZ_ASSERT(!aLog->mFile); + aLog->mFile = CreateTempFile(incomplete.get()); + if (NS_WARN_IF(!aLog->mFile)) { + return NS_ERROR_UNEXPECTED; + } + + MOZ_ASSERT(!aLog->mStream); + nsresult rv = aLog->mFile->OpenANSIFileDesc("w", &aLog->mStream); + if (NS_WARN_IF(NS_FAILED(rv))) { + return NS_ERROR_UNEXPECTED; + } + MozillaRegisterDebugFILE(aLog->mStream); + return NS_OK; + } + + nsresult CloseLog(FileInfo* aLog, const nsAString& aCollectorKind) + { + MOZ_ASSERT(aLog->mStream); + MOZ_ASSERT(aLog->mFile); + + MozillaUnRegisterDebugFILE(aLog->mStream); + fclose(aLog->mStream); + aLog->mStream = nullptr; + + // Strip off "incomplete-". + nsCOMPtr<nsIFile> logFileFinalDestination = + CreateTempFile(aLog->mPrefix); + if (NS_WARN_IF(!logFileFinalDestination)) { + return NS_ERROR_UNEXPECTED; + } + + nsAutoString logFileFinalDestinationName; + logFileFinalDestination->GetLeafName(logFileFinalDestinationName); + if (NS_WARN_IF(logFileFinalDestinationName.IsEmpty())) { + return NS_ERROR_UNEXPECTED; + } + + aLog->mFile->MoveTo(/* directory */ nullptr, logFileFinalDestinationName); + + // Save the file path. + aLog->mFile = logFileFinalDestination; + + // Log to the error console. + nsAutoString logPath; + logFileFinalDestination->GetPath(logPath); + nsAutoString msg = aCollectorKind + + NS_LITERAL_STRING(" Collector log dumped to ") + logPath; + + // We don't want any JS to run between ScanRoots and CollectWhite calls, + // and since ScanRoots calls this method, better to log the message + // asynchronously. + RefPtr<LogStringMessageAsync> log = new LogStringMessageAsync(msg); + NS_DispatchToCurrentThread(log); + return NS_OK; + } + + int32_t mProcessIdentifier; + nsString mFilenameIdentifier; + FileInfo mGCLog; + FileInfo mCCLog; +}; + +NS_IMPL_ISUPPORTS(nsCycleCollectorLogSinkToFile, nsICycleCollectorLogSink) + + +class nsCycleCollectorLogger final : public nsICycleCollectorListener +{ + ~nsCycleCollectorLogger() + { + ClearDescribers(); + } + +public: + nsCycleCollectorLogger() + : mLogSink(nsCycleCollector_createLogSink()) + , mWantAllTraces(false) + , mDisableLog(false) + , mWantAfterProcessing(false) + , mCCLog(nullptr) + { + } + + NS_DECL_ISUPPORTS + + void SetAllTraces() + { + mWantAllTraces = true; + } + + bool IsAllTraces() + { + return mWantAllTraces; + } + + NS_IMETHOD AllTraces(nsICycleCollectorListener** aListener) override + { + SetAllTraces(); + NS_ADDREF(*aListener = this); + return NS_OK; + } + + NS_IMETHOD GetWantAllTraces(bool* aAllTraces) override + { + *aAllTraces = mWantAllTraces; + return NS_OK; + } + + NS_IMETHOD GetDisableLog(bool* aDisableLog) override + { + *aDisableLog = mDisableLog; + return NS_OK; + } + + NS_IMETHOD SetDisableLog(bool aDisableLog) override + { + mDisableLog = aDisableLog; + return NS_OK; + } + + NS_IMETHOD GetWantAfterProcessing(bool* aWantAfterProcessing) override + { + *aWantAfterProcessing = mWantAfterProcessing; + return NS_OK; + } + + NS_IMETHOD SetWantAfterProcessing(bool aWantAfterProcessing) override + { + mWantAfterProcessing = aWantAfterProcessing; + return NS_OK; + } + + NS_IMETHOD GetLogSink(nsICycleCollectorLogSink** aLogSink) override + { + NS_ADDREF(*aLogSink = mLogSink); + return NS_OK; + } + + NS_IMETHOD SetLogSink(nsICycleCollectorLogSink* aLogSink) override + { + if (!aLogSink) { + return NS_ERROR_INVALID_ARG; + } + mLogSink = aLogSink; + return NS_OK; + } + + nsresult Begin() + { + nsresult rv; + + mCurrentAddress.AssignLiteral("0x"); + ClearDescribers(); + if (mDisableLog) { + return NS_OK; + } + + FILE* gcLog; + rv = mLogSink->Open(&gcLog, &mCCLog); + NS_ENSURE_SUCCESS(rv, rv); + // Dump the JS heap. + CollectorData* data = sCollectorData.get(); + if (data && data->mContext) { + data->mContext->DumpJSHeap(gcLog); + } + rv = mLogSink->CloseGCLog(); + NS_ENSURE_SUCCESS(rv, rv); + + fprintf(mCCLog, "# WantAllTraces=%s\n", mWantAllTraces ? "true" : "false"); + return NS_OK; + } + void NoteRefCountedObject(uint64_t aAddress, uint32_t aRefCount, + const char* aObjectDescription) + { + if (!mDisableLog) { + fprintf(mCCLog, "%p [rc=%u] %s\n", (void*)aAddress, aRefCount, + aObjectDescription); + } + if (mWantAfterProcessing) { + CCGraphDescriber* d = new CCGraphDescriber(); + mDescribers.insertBack(d); + mCurrentAddress.AssignLiteral("0x"); + mCurrentAddress.AppendInt(aAddress, 16); + d->mType = CCGraphDescriber::eRefCountedObject; + d->mAddress = mCurrentAddress; + d->mCnt = aRefCount; + d->mName.Append(aObjectDescription); + } + } + void NoteGCedObject(uint64_t aAddress, bool aMarked, + const char* aObjectDescription, + uint64_t aCompartmentAddress) + { + if (!mDisableLog) { + fprintf(mCCLog, "%p [gc%s] %s\n", (void*)aAddress, + aMarked ? ".marked" : "", aObjectDescription); + } + if (mWantAfterProcessing) { + CCGraphDescriber* d = new CCGraphDescriber(); + mDescribers.insertBack(d); + mCurrentAddress.AssignLiteral("0x"); + mCurrentAddress.AppendInt(aAddress, 16); + d->mType = aMarked ? CCGraphDescriber::eGCMarkedObject : + CCGraphDescriber::eGCedObject; + d->mAddress = mCurrentAddress; + d->mName.Append(aObjectDescription); + if (aCompartmentAddress) { + d->mCompartmentOrToAddress.AssignLiteral("0x"); + d->mCompartmentOrToAddress.AppendInt(aCompartmentAddress, 16); + } else { + d->mCompartmentOrToAddress.SetIsVoid(true); + } + } + } + void NoteEdge(uint64_t aToAddress, const char* aEdgeName) + { + if (!mDisableLog) { + fprintf(mCCLog, "> %p %s\n", (void*)aToAddress, aEdgeName); + } + if (mWantAfterProcessing) { + CCGraphDescriber* d = new CCGraphDescriber(); + mDescribers.insertBack(d); + d->mType = CCGraphDescriber::eEdge; + d->mAddress = mCurrentAddress; + d->mCompartmentOrToAddress.AssignLiteral("0x"); + d->mCompartmentOrToAddress.AppendInt(aToAddress, 16); + d->mName.Append(aEdgeName); + } + } + void NoteWeakMapEntry(uint64_t aMap, uint64_t aKey, + uint64_t aKeyDelegate, uint64_t aValue) + { + if (!mDisableLog) { + fprintf(mCCLog, "WeakMapEntry map=%p key=%p keyDelegate=%p value=%p\n", + (void*)aMap, (void*)aKey, (void*)aKeyDelegate, (void*)aValue); + } + // We don't support after-processing for weak map entries. + } + void NoteIncrementalRoot(uint64_t aAddress) + { + if (!mDisableLog) { + fprintf(mCCLog, "IncrementalRoot %p\n", (void*)aAddress); + } + // We don't support after-processing for incremental roots. + } + void BeginResults() + { + if (!mDisableLog) { + fputs("==========\n", mCCLog); + } + } + void DescribeRoot(uint64_t aAddress, uint32_t aKnownEdges) + { + if (!mDisableLog) { + fprintf(mCCLog, "%p [known=%u]\n", (void*)aAddress, aKnownEdges); + } + if (mWantAfterProcessing) { + CCGraphDescriber* d = new CCGraphDescriber(); + mDescribers.insertBack(d); + d->mType = CCGraphDescriber::eRoot; + d->mAddress.AppendInt(aAddress, 16); + d->mCnt = aKnownEdges; + } + } + void DescribeGarbage(uint64_t aAddress) + { + if (!mDisableLog) { + fprintf(mCCLog, "%p [garbage]\n", (void*)aAddress); + } + if (mWantAfterProcessing) { + CCGraphDescriber* d = new CCGraphDescriber(); + mDescribers.insertBack(d); + d->mType = CCGraphDescriber::eGarbage; + d->mAddress.AppendInt(aAddress, 16); + } + } + void End() + { + if (!mDisableLog) { + mCCLog = nullptr; + Unused << NS_WARN_IF(NS_FAILED(mLogSink->CloseCCLog())); + } + } + NS_IMETHOD ProcessNext(nsICycleCollectorHandler* aHandler, + bool* aCanContinue) override + { + if (NS_WARN_IF(!aHandler) || NS_WARN_IF(!mWantAfterProcessing)) { + return NS_ERROR_UNEXPECTED; + } + CCGraphDescriber* d = mDescribers.popFirst(); + if (d) { + switch (d->mType) { + case CCGraphDescriber::eRefCountedObject: + aHandler->NoteRefCountedObject(d->mAddress, + d->mCnt, + d->mName); + break; + case CCGraphDescriber::eGCedObject: + case CCGraphDescriber::eGCMarkedObject: + aHandler->NoteGCedObject(d->mAddress, + d->mType == + CCGraphDescriber::eGCMarkedObject, + d->mName, + d->mCompartmentOrToAddress); + break; + case CCGraphDescriber::eEdge: + aHandler->NoteEdge(d->mAddress, + d->mCompartmentOrToAddress, + d->mName); + break; + case CCGraphDescriber::eRoot: + aHandler->DescribeRoot(d->mAddress, + d->mCnt); + break; + case CCGraphDescriber::eGarbage: + aHandler->DescribeGarbage(d->mAddress); + break; + case CCGraphDescriber::eUnknown: + NS_NOTREACHED("CCGraphDescriber::eUnknown"); + break; + } + delete d; + } + if (!(*aCanContinue = !mDescribers.isEmpty())) { + mCurrentAddress.AssignLiteral("0x"); + } + return NS_OK; + } + NS_IMETHOD AsLogger(nsCycleCollectorLogger** aRetVal) override + { + RefPtr<nsCycleCollectorLogger> rval = this; + rval.forget(aRetVal); + return NS_OK; + } +private: + void ClearDescribers() + { + CCGraphDescriber* d; + while ((d = mDescribers.popFirst())) { + delete d; + } + } + + nsCOMPtr<nsICycleCollectorLogSink> mLogSink; + bool mWantAllTraces; + bool mDisableLog; + bool mWantAfterProcessing; + nsCString mCurrentAddress; + mozilla::LinkedList<CCGraphDescriber> mDescribers; + FILE* mCCLog; +}; + +NS_IMPL_ISUPPORTS(nsCycleCollectorLogger, nsICycleCollectorListener) + +nsresult +nsCycleCollectorLoggerConstructor(nsISupports* aOuter, + const nsIID& aIID, + void** aInstancePtr) +{ + if (NS_WARN_IF(aOuter)) { + return NS_ERROR_NO_AGGREGATION; + } + + nsISupports* logger = new nsCycleCollectorLogger(); + + return logger->QueryInterface(aIID, aInstancePtr); +} + +static bool +GCThingIsGrayCCThing(JS::GCCellPtr thing) +{ + return AddToCCKind(thing.kind()) && + JS::GCThingIsMarkedGray(thing); +} + +static bool +ValueIsGrayCCThing(const JS::Value& value) +{ + return AddToCCKind(value.traceKind()) && + JS::GCThingIsMarkedGray(value.toGCCellPtr()); +} + +//////////////////////////////////////////////////////////////////////// +// Bacon & Rajan's |MarkRoots| routine. +//////////////////////////////////////////////////////////////////////// + +class CCGraphBuilder final : public nsCycleCollectionTraversalCallback, + public nsCycleCollectionNoteRootCallback +{ +private: + CCGraph& mGraph; + CycleCollectorResults& mResults; + NodePool::Builder mNodeBuilder; + EdgePool::Builder mEdgeBuilder; + MOZ_INIT_OUTSIDE_CTOR PtrInfo* mCurrPi; + nsCycleCollectionParticipant* mJSParticipant; + nsCycleCollectionParticipant* mJSZoneParticipant; + nsCString mNextEdgeName; + RefPtr<nsCycleCollectorLogger> mLogger; + bool mMergeZones; + nsAutoPtr<NodePool::Enumerator> mCurrNode; + +public: + CCGraphBuilder(CCGraph& aGraph, + CycleCollectorResults& aResults, + CycleCollectedJSContext* aJSContext, + nsCycleCollectorLogger* aLogger, + bool aMergeZones); + virtual ~CCGraphBuilder(); + + bool WantAllTraces() const + { + return nsCycleCollectionNoteRootCallback::WantAllTraces(); + } + + bool AddPurpleRoot(void* aRoot, nsCycleCollectionParticipant* aParti); + + // This is called when all roots have been added to the graph, to prepare for BuildGraph(). + void DoneAddingRoots(); + + // Do some work traversing nodes in the graph. Returns true if this graph building is finished. + bool BuildGraph(SliceBudget& aBudget); + +private: + PtrInfo* AddNode(void* aPtr, nsCycleCollectionParticipant* aParticipant); + PtrInfo* AddWeakMapNode(JS::GCCellPtr aThing); + PtrInfo* AddWeakMapNode(JSObject* aObject); + + void SetFirstChild() + { + mCurrPi->SetFirstChild(mEdgeBuilder.Mark()); + } + + void SetLastChild() + { + mCurrPi->SetLastChild(mEdgeBuilder.Mark()); + } + +public: + // nsCycleCollectionNoteRootCallback methods. + NS_IMETHOD_(void) NoteXPCOMRoot(nsISupports* aRoot); + NS_IMETHOD_(void) NoteJSRoot(JSObject* aRoot); + NS_IMETHOD_(void) NoteNativeRoot(void* aRoot, + nsCycleCollectionParticipant* aParticipant); + NS_IMETHOD_(void) NoteWeakMapping(JSObject* aMap, JS::GCCellPtr aKey, + JSObject* aKdelegate, JS::GCCellPtr aVal); + + // nsCycleCollectionTraversalCallback methods. + NS_IMETHOD_(void) DescribeRefCountedNode(nsrefcnt aRefCount, + const char* aObjName); + NS_IMETHOD_(void) DescribeGCedNode(bool aIsMarked, const char* aObjName, + uint64_t aCompartmentAddress); + + NS_IMETHOD_(void) NoteXPCOMChild(nsISupports* aChild); + NS_IMETHOD_(void) NoteJSChild(const JS::GCCellPtr& aThing); + NS_IMETHOD_(void) NoteNativeChild(void* aChild, + nsCycleCollectionParticipant* aParticipant); + NS_IMETHOD_(void) NoteNextEdgeName(const char* aName); + +private: + void NoteJSChild(JS::GCCellPtr aChild); + + NS_IMETHOD_(void) NoteRoot(void* aRoot, + nsCycleCollectionParticipant* aParticipant) + { + MOZ_ASSERT(aRoot); + MOZ_ASSERT(aParticipant); + + if (!aParticipant->CanSkipInCC(aRoot) || MOZ_UNLIKELY(WantAllTraces())) { + AddNode(aRoot, aParticipant); + } + } + + NS_IMETHOD_(void) NoteChild(void* aChild, nsCycleCollectionParticipant* aCp, + nsCString& aEdgeName) + { + PtrInfo* childPi = AddNode(aChild, aCp); + if (!childPi) { + return; + } + mEdgeBuilder.Add(childPi); + if (mLogger) { + mLogger->NoteEdge((uint64_t)aChild, aEdgeName.get()); + } + ++childPi->mInternalRefs; + } + + JS::Zone* MergeZone(JS::GCCellPtr aGcthing) + { + if (!mMergeZones) { + return nullptr; + } + JS::Zone* zone = JS::GetTenuredGCThingZone(aGcthing); + if (js::IsSystemZone(zone)) { + return nullptr; + } + return zone; + } +}; + +CCGraphBuilder::CCGraphBuilder(CCGraph& aGraph, + CycleCollectorResults& aResults, + CycleCollectedJSContext* aJSContext, + nsCycleCollectorLogger* aLogger, + bool aMergeZones) + : mGraph(aGraph) + , mResults(aResults) + , mNodeBuilder(aGraph.mNodes) + , mEdgeBuilder(aGraph.mEdges) + , mJSParticipant(nullptr) + , mJSZoneParticipant(nullptr) + , mLogger(aLogger) + , mMergeZones(aMergeZones) +{ + if (aJSContext) { + mJSParticipant = aJSContext->GCThingParticipant(); + mJSZoneParticipant = aJSContext->ZoneParticipant(); + } + + if (mLogger) { + mFlags |= nsCycleCollectionTraversalCallback::WANT_DEBUG_INFO; + if (mLogger->IsAllTraces()) { + mFlags |= nsCycleCollectionTraversalCallback::WANT_ALL_TRACES; + mWantAllTraces = true; // for nsCycleCollectionNoteRootCallback + } + } + + mMergeZones = mMergeZones && MOZ_LIKELY(!WantAllTraces()); + + MOZ_ASSERT(nsCycleCollectionNoteRootCallback::WantAllTraces() == + nsCycleCollectionTraversalCallback::WantAllTraces()); +} + +CCGraphBuilder::~CCGraphBuilder() +{ +} + +PtrInfo* +CCGraphBuilder::AddNode(void* aPtr, nsCycleCollectionParticipant* aParticipant) +{ + PtrToNodeEntry* e = mGraph.AddNodeToMap(aPtr); + if (!e) { + return nullptr; + } + + PtrInfo* result; + if (!e->mNode) { + // New entry. + result = mNodeBuilder.Add(aPtr, aParticipant); + if (!result) { + return nullptr; + } + + e->mNode = result; + NS_ASSERTION(result, "mNodeBuilder.Add returned null"); + } else { + result = e->mNode; + MOZ_ASSERT(result->mParticipant == aParticipant, + "nsCycleCollectionParticipant shouldn't change!"); + } + return result; +} + +bool +CCGraphBuilder::AddPurpleRoot(void* aRoot, nsCycleCollectionParticipant* aParti) +{ + CanonicalizeParticipant(&aRoot, &aParti); + + if (WantAllTraces() || !aParti->CanSkipInCC(aRoot)) { + PtrInfo* pinfo = AddNode(aRoot, aParti); + if (!pinfo) { + return false; + } + } + + return true; +} + +void +CCGraphBuilder::DoneAddingRoots() +{ + // We've finished adding roots, and everything in the graph is a root. + mGraph.mRootCount = mGraph.MapCount(); + + mCurrNode = new NodePool::Enumerator(mGraph.mNodes); +} + +MOZ_NEVER_INLINE bool +CCGraphBuilder::BuildGraph(SliceBudget& aBudget) +{ + const intptr_t kNumNodesBetweenTimeChecks = 1000; + const intptr_t kStep = SliceBudget::CounterReset / kNumNodesBetweenTimeChecks; + + MOZ_ASSERT(mCurrNode); + + while (!aBudget.isOverBudget() && !mCurrNode->IsDone()) { + PtrInfo* pi = mCurrNode->GetNext(); + if (!pi) { + MOZ_CRASH(); + } + + mCurrPi = pi; + + // We need to call SetFirstChild() even on deleted nodes, to set their + // firstChild() that may be read by a prior non-deleted neighbor. + SetFirstChild(); + + if (pi->mParticipant) { + nsresult rv = pi->mParticipant->Traverse(pi->mPointer, *this); + MOZ_RELEASE_ASSERT(!NS_FAILED(rv), "Cycle collector Traverse method failed"); + } + + if (mCurrNode->AtBlockEnd()) { + SetLastChild(); + } + + aBudget.step(kStep); + } + + if (!mCurrNode->IsDone()) { + return false; + } + + if (mGraph.mRootCount > 0) { + SetLastChild(); + } + + mCurrNode = nullptr; + + return true; +} + +NS_IMETHODIMP_(void) +CCGraphBuilder::NoteXPCOMRoot(nsISupports* aRoot) +{ + aRoot = CanonicalizeXPCOMParticipant(aRoot); + NS_ASSERTION(aRoot, + "Don't add objects that don't participate in collection!"); + + nsXPCOMCycleCollectionParticipant* cp; + ToParticipant(aRoot, &cp); + + NoteRoot(aRoot, cp); +} + +NS_IMETHODIMP_(void) +CCGraphBuilder::NoteJSRoot(JSObject* aRoot) +{ + if (JS::Zone* zone = MergeZone(JS::GCCellPtr(aRoot))) { + NoteRoot(zone, mJSZoneParticipant); + } else { + NoteRoot(aRoot, mJSParticipant); + } +} + +NS_IMETHODIMP_(void) +CCGraphBuilder::NoteNativeRoot(void* aRoot, + nsCycleCollectionParticipant* aParticipant) +{ + NoteRoot(aRoot, aParticipant); +} + +NS_IMETHODIMP_(void) +CCGraphBuilder::DescribeRefCountedNode(nsrefcnt aRefCount, const char* aObjName) +{ + MOZ_RELEASE_ASSERT(aRefCount != 0, "CCed refcounted object has zero refcount"); + MOZ_RELEASE_ASSERT(aRefCount != UINT32_MAX, "CCed refcounted object has overflowing refcount"); + + mResults.mVisitedRefCounted++; + + if (mLogger) { + mLogger->NoteRefCountedObject((uint64_t)mCurrPi->mPointer, aRefCount, + aObjName); + } + + mCurrPi->mRefCount = aRefCount; +} + +NS_IMETHODIMP_(void) +CCGraphBuilder::DescribeGCedNode(bool aIsMarked, const char* aObjName, + uint64_t aCompartmentAddress) +{ + uint32_t refCount = aIsMarked ? UINT32_MAX : 0; + mResults.mVisitedGCed++; + + if (mLogger) { + mLogger->NoteGCedObject((uint64_t)mCurrPi->mPointer, aIsMarked, + aObjName, aCompartmentAddress); + } + + mCurrPi->mRefCount = refCount; +} + +NS_IMETHODIMP_(void) +CCGraphBuilder::NoteXPCOMChild(nsISupports* aChild) +{ + nsCString edgeName; + if (WantDebugInfo()) { + edgeName.Assign(mNextEdgeName); + mNextEdgeName.Truncate(); + } + if (!aChild || !(aChild = CanonicalizeXPCOMParticipant(aChild))) { + return; + } + + nsXPCOMCycleCollectionParticipant* cp; + ToParticipant(aChild, &cp); + if (cp && (!cp->CanSkipThis(aChild) || WantAllTraces())) { + NoteChild(aChild, cp, edgeName); + } +} + +NS_IMETHODIMP_(void) +CCGraphBuilder::NoteNativeChild(void* aChild, + nsCycleCollectionParticipant* aParticipant) +{ + nsCString edgeName; + if (WantDebugInfo()) { + edgeName.Assign(mNextEdgeName); + mNextEdgeName.Truncate(); + } + if (!aChild) { + return; + } + + MOZ_ASSERT(aParticipant, "Need a nsCycleCollectionParticipant!"); + if (!aParticipant->CanSkipThis(aChild) || WantAllTraces()) { + NoteChild(aChild, aParticipant, edgeName); + } +} + +NS_IMETHODIMP_(void) +CCGraphBuilder::NoteJSChild(const JS::GCCellPtr& aChild) +{ + if (!aChild) { + return; + } + + nsCString edgeName; + if (MOZ_UNLIKELY(WantDebugInfo())) { + edgeName.Assign(mNextEdgeName); + mNextEdgeName.Truncate(); + } + + if (GCThingIsGrayCCThing(aChild) || MOZ_UNLIKELY(WantAllTraces())) { + if (JS::Zone* zone = MergeZone(aChild)) { + NoteChild(zone, mJSZoneParticipant, edgeName); + } else { + NoteChild(aChild.asCell(), mJSParticipant, edgeName); + } + } +} + +NS_IMETHODIMP_(void) +CCGraphBuilder::NoteNextEdgeName(const char* aName) +{ + if (WantDebugInfo()) { + mNextEdgeName = aName; + } +} + +PtrInfo* +CCGraphBuilder::AddWeakMapNode(JS::GCCellPtr aNode) +{ + MOZ_ASSERT(aNode, "Weak map node should be non-null."); + + if (!GCThingIsGrayCCThing(aNode) && !WantAllTraces()) { + return nullptr; + } + + if (JS::Zone* zone = MergeZone(aNode)) { + return AddNode(zone, mJSZoneParticipant); + } + return AddNode(aNode.asCell(), mJSParticipant); +} + +PtrInfo* +CCGraphBuilder::AddWeakMapNode(JSObject* aObject) +{ + return AddWeakMapNode(JS::GCCellPtr(aObject)); +} + +NS_IMETHODIMP_(void) +CCGraphBuilder::NoteWeakMapping(JSObject* aMap, JS::GCCellPtr aKey, + JSObject* aKdelegate, JS::GCCellPtr aVal) +{ + // Don't try to optimize away the entry here, as we've already attempted to + // do that in TraceWeakMapping in nsXPConnect. + WeakMapping* mapping = mGraph.mWeakMaps.AppendElement(); + mapping->mMap = aMap ? AddWeakMapNode(aMap) : nullptr; + mapping->mKey = aKey ? AddWeakMapNode(aKey) : nullptr; + mapping->mKeyDelegate = aKdelegate ? AddWeakMapNode(aKdelegate) : mapping->mKey; + mapping->mVal = aVal ? AddWeakMapNode(aVal) : nullptr; + + if (mLogger) { + mLogger->NoteWeakMapEntry((uint64_t)aMap, aKey ? aKey.unsafeAsInteger() : 0, + (uint64_t)aKdelegate, + aVal ? aVal.unsafeAsInteger() : 0); + } +} + +static bool +AddPurpleRoot(CCGraphBuilder& aBuilder, void* aRoot, + nsCycleCollectionParticipant* aParti) +{ + return aBuilder.AddPurpleRoot(aRoot, aParti); +} + +// MayHaveChild() will be false after a Traverse if the object does +// not have any children the CC will visit. +class ChildFinder : public nsCycleCollectionTraversalCallback +{ +public: + ChildFinder() : mMayHaveChild(false) + { + } + + // The logic of the Note*Child functions must mirror that of their + // respective functions in CCGraphBuilder. + NS_IMETHOD_(void) NoteXPCOMChild(nsISupports* aChild); + NS_IMETHOD_(void) NoteNativeChild(void* aChild, + nsCycleCollectionParticipant* aHelper); + NS_IMETHOD_(void) NoteJSChild(const JS::GCCellPtr& aThing); + + NS_IMETHOD_(void) DescribeRefCountedNode(nsrefcnt aRefcount, + const char* aObjname) + { + } + NS_IMETHOD_(void) DescribeGCedNode(bool aIsMarked, + const char* aObjname, + uint64_t aCompartmentAddress) + { + } + NS_IMETHOD_(void) NoteNextEdgeName(const char* aName) + { + } + bool MayHaveChild() + { + return mMayHaveChild; + } +private: + bool mMayHaveChild; +}; + +NS_IMETHODIMP_(void) +ChildFinder::NoteXPCOMChild(nsISupports* aChild) +{ + if (!aChild || !(aChild = CanonicalizeXPCOMParticipant(aChild))) { + return; + } + nsXPCOMCycleCollectionParticipant* cp; + ToParticipant(aChild, &cp); + if (cp && !cp->CanSkip(aChild, true)) { + mMayHaveChild = true; + } +} + +NS_IMETHODIMP_(void) +ChildFinder::NoteNativeChild(void* aChild, + nsCycleCollectionParticipant* aHelper) +{ + if (!aChild) { + return; + } + MOZ_ASSERT(aHelper, "Native child must have a participant"); + if (!aHelper->CanSkip(aChild, true)) { + mMayHaveChild = true; + } +} + +NS_IMETHODIMP_(void) +ChildFinder::NoteJSChild(const JS::GCCellPtr& aChild) +{ + if (aChild && JS::GCThingIsMarkedGray(aChild)) { + mMayHaveChild = true; + } +} + +static bool +MayHaveChild(void* aObj, nsCycleCollectionParticipant* aCp) +{ + ChildFinder cf; + aCp->Traverse(aObj, cf); + return cf.MayHaveChild(); +} + +// JSPurpleBuffer keeps references to GCThings which might affect the +// next cycle collection. It is owned only by itself and during unlink its +// self reference is broken down and the object ends up killing itself. +// If GC happens before CC, references to GCthings and the self reference are +// removed. +class JSPurpleBuffer +{ + ~JSPurpleBuffer() + { + MOZ_ASSERT(mValues.IsEmpty()); + MOZ_ASSERT(mObjects.IsEmpty()); + } + +public: + explicit JSPurpleBuffer(RefPtr<JSPurpleBuffer>& aReferenceToThis) + : mReferenceToThis(aReferenceToThis) + , mValues(kSegmentSize) + , mObjects(kSegmentSize) + { + mReferenceToThis = this; + mozilla::HoldJSObjects(this); + } + + void Destroy() + { + mReferenceToThis = nullptr; + mValues.Clear(); + mObjects.Clear(); + mozilla::DropJSObjects(this); + } + + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(JSPurpleBuffer) + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(JSPurpleBuffer) + + RefPtr<JSPurpleBuffer>& mReferenceToThis; + + // These are raw pointers instead of Heap<T> because we only need Heap<T> for + // pointers which may point into the nursery. The purple buffer never contains + // pointers to the nursery because nursery gcthings can never be gray and only + // gray things can be inserted into the purple buffer. + static const size_t kSegmentSize = 512; + SegmentedVector<JS::Value, kSegmentSize, InfallibleAllocPolicy> mValues; + SegmentedVector<JSObject*, kSegmentSize, InfallibleAllocPolicy> mObjects; +}; + +NS_IMPL_CYCLE_COLLECTION_CLASS(JSPurpleBuffer) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(JSPurpleBuffer) + tmp->Destroy(); +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(JSPurpleBuffer) + CycleCollectionNoteChild(cb, tmp, "self"); + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +#define NS_TRACE_SEGMENTED_ARRAY(_field, _type) \ + { \ + for (auto iter = tmp->_field.Iter(); !iter.Done(); iter.Next()) { \ + js::gc::CallTraceCallbackOnNonHeap<_type, TraceCallbacks>( \ + &iter.Get(), aCallbacks, #_field, aClosure); \ + } \ + } + +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(JSPurpleBuffer) + NS_TRACE_SEGMENTED_ARRAY(mValues, JS::Value) + NS_TRACE_SEGMENTED_ARRAY(mObjects, JSObject*) +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(JSPurpleBuffer, AddRef) +NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(JSPurpleBuffer, Release) + +class SnowWhiteKiller : public TraceCallbacks +{ + struct SnowWhiteObject + { + void* mPointer; + nsCycleCollectionParticipant* mParticipant; + nsCycleCollectingAutoRefCnt* mRefCnt; + }; + + // Segments are 4 KiB on 32-bit and 8 KiB on 64-bit. + static const size_t kSegmentSize = sizeof(void*) * 1024; + typedef SegmentedVector<SnowWhiteObject, kSegmentSize, InfallibleAllocPolicy> + ObjectsVector; + +public: + explicit SnowWhiteKiller(nsCycleCollector* aCollector) + : mCollector(aCollector) + , mObjects(kSegmentSize) + { + MOZ_ASSERT(mCollector, "Calling SnowWhiteKiller after nsCC went away"); + } + + ~SnowWhiteKiller() + { + for (auto iter = mObjects.Iter(); !iter.Done(); iter.Next()) { + SnowWhiteObject& o = iter.Get(); + if (!o.mRefCnt->get() && !o.mRefCnt->IsInPurpleBuffer()) { + mCollector->RemoveObjectFromGraph(o.mPointer); + o.mRefCnt->stabilizeForDeletion(); + { + JS::AutoEnterCycleCollection autocc(mCollector->Context()->Context()); + o.mParticipant->Trace(o.mPointer, *this, nullptr); + } + o.mParticipant->DeleteCycleCollectable(o.mPointer); + } + } + } + + void + Visit(nsPurpleBuffer& aBuffer, nsPurpleBufferEntry* aEntry) + { + MOZ_ASSERT(aEntry->mObject, "Null object in purple buffer"); + if (!aEntry->mRefCnt->get()) { + void* o = aEntry->mObject; + nsCycleCollectionParticipant* cp = aEntry->mParticipant; + CanonicalizeParticipant(&o, &cp); + SnowWhiteObject swo = { o, cp, aEntry->mRefCnt }; + mObjects.InfallibleAppend(swo); + aBuffer.Remove(aEntry); + } + } + + bool HasSnowWhiteObjects() const + { + return !mObjects.IsEmpty(); + } + + virtual void Trace(JS::Heap<JS::Value>* aValue, const char* aName, + void* aClosure) const override + { + const JS::Value& val = aValue->unbarrieredGet(); + if (val.isMarkable() && ValueIsGrayCCThing(val)) { + MOZ_ASSERT(!js::gc::IsInsideNursery(val.toGCThing())); + mCollector->GetJSPurpleBuffer()->mValues.InfallibleAppend(val); + } + } + + virtual void Trace(JS::Heap<jsid>* aId, const char* aName, + void* aClosure) const override + { + } + + void AppendJSObjectToPurpleBuffer(JSObject* obj) const + { + if (obj && JS::ObjectIsMarkedGray(obj)) { + MOZ_ASSERT(JS::ObjectIsTenured(obj)); + mCollector->GetJSPurpleBuffer()->mObjects.InfallibleAppend(obj); + } + } + + virtual void Trace(JS::Heap<JSObject*>* aObject, const char* aName, + void* aClosure) const override + { + AppendJSObjectToPurpleBuffer(aObject->unbarrieredGet()); + } + + virtual void Trace(JSObject** aObject, const char* aName, + void* aClosure) const override + { + AppendJSObjectToPurpleBuffer(*aObject); + } + + virtual void Trace(JS::TenuredHeap<JSObject*>* aObject, const char* aName, + void* aClosure) const override + { + AppendJSObjectToPurpleBuffer(aObject->unbarrieredGetPtr()); + } + + virtual void Trace(JS::Heap<JSString*>* aString, const char* aName, + void* aClosure) const override + { + } + + virtual void Trace(JS::Heap<JSScript*>* aScript, const char* aName, + void* aClosure) const override + { + } + + virtual void Trace(JS::Heap<JSFunction*>* aFunction, const char* aName, + void* aClosure) const override + { + } + +private: + RefPtr<nsCycleCollector> mCollector; + ObjectsVector mObjects; +}; + +class RemoveSkippableVisitor : public SnowWhiteKiller +{ +public: + RemoveSkippableVisitor(nsCycleCollector* aCollector, + bool aRemoveChildlessNodes, + bool aAsyncSnowWhiteFreeing, + CC_ForgetSkippableCallback aCb) + : SnowWhiteKiller(aCollector) + , mRemoveChildlessNodes(aRemoveChildlessNodes) + , mAsyncSnowWhiteFreeing(aAsyncSnowWhiteFreeing) + , mDispatchedDeferredDeletion(false) + , mCallback(aCb) + { + } + + ~RemoveSkippableVisitor() + { + // Note, we must call the callback before SnowWhiteKiller calls + // DeleteCycleCollectable! + if (mCallback) { + mCallback(); + } + if (HasSnowWhiteObjects()) { + // Effectively a continuation. + nsCycleCollector_dispatchDeferredDeletion(true); + } + } + + void + Visit(nsPurpleBuffer& aBuffer, nsPurpleBufferEntry* aEntry) + { + MOZ_ASSERT(aEntry->mObject, "null mObject in purple buffer"); + if (!aEntry->mRefCnt->get()) { + if (!mAsyncSnowWhiteFreeing) { + SnowWhiteKiller::Visit(aBuffer, aEntry); + } else if (!mDispatchedDeferredDeletion) { + mDispatchedDeferredDeletion = true; + nsCycleCollector_dispatchDeferredDeletion(false); + } + return; + } + void* o = aEntry->mObject; + nsCycleCollectionParticipant* cp = aEntry->mParticipant; + CanonicalizeParticipant(&o, &cp); + if (aEntry->mRefCnt->IsPurple() && !cp->CanSkip(o, false) && + (!mRemoveChildlessNodes || MayHaveChild(o, cp))) { + return; + } + aBuffer.Remove(aEntry); + } + +private: + bool mRemoveChildlessNodes; + bool mAsyncSnowWhiteFreeing; + bool mDispatchedDeferredDeletion; + CC_ForgetSkippableCallback mCallback; +}; + +void +nsPurpleBuffer::RemoveSkippable(nsCycleCollector* aCollector, + bool aRemoveChildlessNodes, + bool aAsyncSnowWhiteFreeing, + CC_ForgetSkippableCallback aCb) +{ + RemoveSkippableVisitor visitor(aCollector, aRemoveChildlessNodes, + aAsyncSnowWhiteFreeing, aCb); + VisitEntries(visitor); +} + +bool +nsCycleCollector::FreeSnowWhite(bool aUntilNoSWInPurpleBuffer) +{ + CheckThreadSafety(); + + if (mFreeingSnowWhite) { + return false; + } + + AutoRestore<bool> ar(mFreeingSnowWhite); + mFreeingSnowWhite = true; + + bool hadSnowWhiteObjects = false; + do { + SnowWhiteKiller visitor(this); + mPurpleBuf.VisitEntries(visitor); + hadSnowWhiteObjects = hadSnowWhiteObjects || + visitor.HasSnowWhiteObjects(); + if (!visitor.HasSnowWhiteObjects()) { + break; + } + } while (aUntilNoSWInPurpleBuffer); + return hadSnowWhiteObjects; +} + +void +nsCycleCollector::ForgetSkippable(bool aRemoveChildlessNodes, + bool aAsyncSnowWhiteFreeing) +{ + CheckThreadSafety(); + + mozilla::Maybe<mozilla::AutoGlobalTimelineMarker> marker; + if (NS_IsMainThread()) { + marker.emplace("nsCycleCollector::ForgetSkippable", MarkerStackRequest::NO_STACK); + } + + // If we remove things from the purple buffer during graph building, we may + // lose track of an object that was mutated during graph building. + MOZ_ASSERT(IsIdle()); + + if (mJSContext) { + mJSContext->PrepareForForgetSkippable(); + } + MOZ_ASSERT(!mScanInProgress, + "Don't forget skippable or free snow-white while scan is in progress."); + mPurpleBuf.RemoveSkippable(this, aRemoveChildlessNodes, + aAsyncSnowWhiteFreeing, mForgetSkippableCB); +} + +MOZ_NEVER_INLINE void +nsCycleCollector::MarkRoots(SliceBudget& aBudget) +{ + JS::AutoAssertNoGC nogc; + TimeLog timeLog; + AutoRestore<bool> ar(mScanInProgress); + MOZ_RELEASE_ASSERT(!mScanInProgress); + mScanInProgress = true; + MOZ_ASSERT(mIncrementalPhase == GraphBuildingPhase); + + JS::AutoEnterCycleCollection autocc(Context()->Context()); + bool doneBuilding = mBuilder->BuildGraph(aBudget); + + if (!doneBuilding) { + timeLog.Checkpoint("MarkRoots()"); + return; + } + + mBuilder = nullptr; + mIncrementalPhase = ScanAndCollectWhitePhase; + timeLog.Checkpoint("MarkRoots()"); +} + + +//////////////////////////////////////////////////////////////////////// +// Bacon & Rajan's |ScanRoots| routine. +//////////////////////////////////////////////////////////////////////// + + +struct ScanBlackVisitor +{ + ScanBlackVisitor(uint32_t& aWhiteNodeCount, bool& aFailed) + : mWhiteNodeCount(aWhiteNodeCount), mFailed(aFailed) + { + } + + bool ShouldVisitNode(PtrInfo const* aPi) + { + return aPi->mColor != black; + } + + MOZ_NEVER_INLINE void VisitNode(PtrInfo* aPi) + { + if (aPi->mColor == white) { + --mWhiteNodeCount; + } + aPi->mColor = black; + } + + void Failed() + { + mFailed = true; + } + +private: + uint32_t& mWhiteNodeCount; + bool& mFailed; +}; + +static void +FloodBlackNode(uint32_t& aWhiteNodeCount, bool& aFailed, PtrInfo* aPi) +{ + GraphWalker<ScanBlackVisitor>(ScanBlackVisitor(aWhiteNodeCount, + aFailed)).Walk(aPi); + MOZ_ASSERT(aPi->mColor == black || !aPi->WasTraversed(), + "FloodBlackNode should make aPi black"); +} + +// Iterate over the WeakMaps. If we mark anything while iterating +// over the WeakMaps, we must iterate over all of the WeakMaps again. +void +nsCycleCollector::ScanWeakMaps() +{ + bool anyChanged; + bool failed = false; + do { + anyChanged = false; + for (uint32_t i = 0; i < mGraph.mWeakMaps.Length(); i++) { + WeakMapping* wm = &mGraph.mWeakMaps[i]; + + // If any of these are null, the original object was marked black. + uint32_t mColor = wm->mMap ? wm->mMap->mColor : black; + uint32_t kColor = wm->mKey ? wm->mKey->mColor : black; + uint32_t kdColor = wm->mKeyDelegate ? wm->mKeyDelegate->mColor : black; + uint32_t vColor = wm->mVal ? wm->mVal->mColor : black; + + MOZ_ASSERT(mColor != grey, "Uncolored weak map"); + MOZ_ASSERT(kColor != grey, "Uncolored weak map key"); + MOZ_ASSERT(kdColor != grey, "Uncolored weak map key delegate"); + MOZ_ASSERT(vColor != grey, "Uncolored weak map value"); + + if (mColor == black && kColor != black && kdColor == black) { + FloodBlackNode(mWhiteNodeCount, failed, wm->mKey); + anyChanged = true; + } + + if (mColor == black && kColor == black && vColor != black) { + FloodBlackNode(mWhiteNodeCount, failed, wm->mVal); + anyChanged = true; + } + } + } while (anyChanged); + + if (failed) { + MOZ_ASSERT(false, "Ran out of memory in ScanWeakMaps"); + CC_TELEMETRY(_OOM, true); + } +} + +// Flood black from any objects in the purple buffer that are in the CC graph. +class PurpleScanBlackVisitor +{ +public: + PurpleScanBlackVisitor(CCGraph& aGraph, nsCycleCollectorLogger* aLogger, + uint32_t& aCount, bool& aFailed) + : mGraph(aGraph), mLogger(aLogger), mCount(aCount), mFailed(aFailed) + { + } + + void + Visit(nsPurpleBuffer& aBuffer, nsPurpleBufferEntry* aEntry) + { + MOZ_ASSERT(aEntry->mObject, + "Entries with null mObject shouldn't be in the purple buffer."); + MOZ_ASSERT(aEntry->mRefCnt->get() != 0, + "Snow-white objects shouldn't be in the purple buffer."); + + void* obj = aEntry->mObject; + if (!aEntry->mParticipant) { + obj = CanonicalizeXPCOMParticipant(static_cast<nsISupports*>(obj)); + MOZ_ASSERT(obj, "Don't add objects that don't participate in collection!"); + } + + PtrInfo* pi = mGraph.FindNode(obj); + if (!pi) { + return; + } + MOZ_ASSERT(pi->mParticipant, "No dead objects should be in the purple buffer."); + if (MOZ_UNLIKELY(mLogger)) { + mLogger->NoteIncrementalRoot((uint64_t)pi->mPointer); + } + if (pi->mColor == black) { + return; + } + FloodBlackNode(mCount, mFailed, pi); + } + +private: + CCGraph& mGraph; + RefPtr<nsCycleCollectorLogger> mLogger; + uint32_t& mCount; + bool& mFailed; +}; + +// Objects that have been stored somewhere since the start of incremental graph building must +// be treated as live for this cycle collection, because we may not have accurate information +// about who holds references to them. +void +nsCycleCollector::ScanIncrementalRoots() +{ + TimeLog timeLog; + + // Reference counted objects: + // We cleared the purple buffer at the start of the current ICC, so if a + // refcounted object is purple, it may have been AddRef'd during the current + // ICC. (It may also have only been released.) If that is the case, we cannot + // be sure that the set of things pointing to the object in the CC graph + // is accurate. Therefore, for safety, we treat any purple objects as being + // live during the current CC. We don't remove anything from the purple + // buffer here, so these objects will be suspected and freed in the next CC + // if they are garbage. + bool failed = false; + PurpleScanBlackVisitor purpleScanBlackVisitor(mGraph, mLogger, + mWhiteNodeCount, failed); + mPurpleBuf.VisitEntries(purpleScanBlackVisitor); + timeLog.Checkpoint("ScanIncrementalRoots::fix purple"); + + bool hasJSContext = !!mJSContext; + nsCycleCollectionParticipant* jsParticipant = + hasJSContext ? mJSContext->GCThingParticipant() : nullptr; + nsCycleCollectionParticipant* zoneParticipant = + hasJSContext ? mJSContext->ZoneParticipant() : nullptr; + bool hasLogger = !!mLogger; + + NodePool::Enumerator etor(mGraph.mNodes); + while (!etor.IsDone()) { + PtrInfo* pi = etor.GetNext(); + + // As an optimization, if an object has already been determined to be live, + // don't consider it further. We can't do this if there is a listener, + // because the listener wants to know the complete set of incremental roots. + if (pi->mColor == black && MOZ_LIKELY(!hasLogger)) { + continue; + } + + // Garbage collected objects: + // If a GCed object was added to the graph with a refcount of zero, and is + // now marked black by the GC, it was probably gray before and was exposed + // to active JS, so it may have been stored somewhere, so it needs to be + // treated as live. + if (pi->IsGrayJS() && MOZ_LIKELY(hasJSContext)) { + // If the object is still marked gray by the GC, nothing could have gotten + // hold of it, so it isn't an incremental root. + if (pi->mParticipant == jsParticipant) { + JS::GCCellPtr ptr(pi->mPointer, JS::GCThingTraceKind(pi->mPointer)); + if (GCThingIsGrayCCThing(ptr)) { + continue; + } + } else if (pi->mParticipant == zoneParticipant) { + JS::Zone* zone = static_cast<JS::Zone*>(pi->mPointer); + if (js::ZoneGlobalsAreAllGray(zone)) { + continue; + } + } else { + MOZ_ASSERT(false, "Non-JS thing with 0 refcount? Treating as live."); + } + } else if (!pi->mParticipant && pi->WasTraversed()) { + // Dead traversed refcounted objects: + // If the object was traversed, it must have been alive at the start of + // the CC, and thus had a positive refcount. It is dead now, so its + // refcount must have decreased at some point during the CC. Therefore, + // it would be in the purple buffer if it wasn't dead, so treat it as an + // incremental root. + // + // This should not cause leaks because as the object died it should have + // released anything it held onto, which will add them to the purple + // buffer, which will cause them to be considered in the next CC. + } else { + continue; + } + + // At this point, pi must be an incremental root. + + // If there's a listener, tell it about this root. We don't bother with the + // optimization of skipping the Walk() if pi is black: it will just return + // without doing anything and there's no need to make this case faster. + if (MOZ_UNLIKELY(hasLogger) && pi->mPointer) { + // Dead objects aren't logged. See bug 1031370. + mLogger->NoteIncrementalRoot((uint64_t)pi->mPointer); + } + + FloodBlackNode(mWhiteNodeCount, failed, pi); + } + + timeLog.Checkpoint("ScanIncrementalRoots::fix nodes"); + + if (failed) { + NS_ASSERTION(false, "Ran out of memory in ScanIncrementalRoots"); + CC_TELEMETRY(_OOM, true); + } +} + +// Mark nodes white and make sure their refcounts are ok. +// No nodes are marked black during this pass to ensure that refcount +// checking is run on all nodes not marked black by ScanIncrementalRoots. +void +nsCycleCollector::ScanWhiteNodes(bool aFullySynchGraphBuild) +{ + NodePool::Enumerator nodeEnum(mGraph.mNodes); + while (!nodeEnum.IsDone()) { + PtrInfo* pi = nodeEnum.GetNext(); + if (pi->mColor == black) { + // Incremental roots can be in a nonsensical state, so don't + // check them. This will miss checking nodes that are merely + // reachable from incremental roots. + MOZ_ASSERT(!aFullySynchGraphBuild, + "In a synch CC, no nodes should be marked black early on."); + continue; + } + MOZ_ASSERT(pi->mColor == grey); + + if (!pi->WasTraversed()) { + // This node was deleted before it was traversed, so there's no reason + // to look at it. + MOZ_ASSERT(!pi->mParticipant, "Live nodes should all have been traversed"); + continue; + } + + if (pi->mInternalRefs == pi->mRefCount || pi->IsGrayJS()) { + pi->mColor = white; + ++mWhiteNodeCount; + continue; + } + + if (pi->mInternalRefs > pi->mRefCount) { +#ifdef MOZ_CRASHREPORTER + const char* piName = "Unknown"; + if (pi->mParticipant) { + piName = pi->mParticipant->ClassName(); + } + nsPrintfCString msg("More references to an object than its refcount, for class %s", piName); + CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("CycleCollector"), msg); +#endif + MOZ_CRASH(); + } + + // This node will get marked black in the next pass. + } +} + +// Any remaining grey nodes that haven't already been deleted must be alive, +// so mark them and their children black. Any nodes that are black must have +// already had their children marked black, so there's no need to look at them +// again. This pass may turn some white nodes to black. +void +nsCycleCollector::ScanBlackNodes() +{ + bool failed = false; + NodePool::Enumerator nodeEnum(mGraph.mNodes); + while (!nodeEnum.IsDone()) { + PtrInfo* pi = nodeEnum.GetNext(); + if (pi->mColor == grey && pi->WasTraversed()) { + FloodBlackNode(mWhiteNodeCount, failed, pi); + } + } + + if (failed) { + NS_ASSERTION(false, "Ran out of memory in ScanBlackNodes"); + CC_TELEMETRY(_OOM, true); + } +} + +void +nsCycleCollector::ScanRoots(bool aFullySynchGraphBuild) +{ + JS::AutoAssertNoGC nogc; + AutoRestore<bool> ar(mScanInProgress); + MOZ_RELEASE_ASSERT(!mScanInProgress); + mScanInProgress = true; + mWhiteNodeCount = 0; + MOZ_ASSERT(mIncrementalPhase == ScanAndCollectWhitePhase); + + JS::AutoEnterCycleCollection autocc(Context()->Context()); + + if (!aFullySynchGraphBuild) { + ScanIncrementalRoots(); + } + + TimeLog timeLog; + ScanWhiteNodes(aFullySynchGraphBuild); + timeLog.Checkpoint("ScanRoots::ScanWhiteNodes"); + + ScanBlackNodes(); + timeLog.Checkpoint("ScanRoots::ScanBlackNodes"); + + // Scanning weak maps must be done last. + ScanWeakMaps(); + timeLog.Checkpoint("ScanRoots::ScanWeakMaps"); + + if (mLogger) { + mLogger->BeginResults(); + + NodePool::Enumerator etor(mGraph.mNodes); + while (!etor.IsDone()) { + PtrInfo* pi = etor.GetNext(); + if (!pi->WasTraversed()) { + continue; + } + switch (pi->mColor) { + case black: + if (!pi->IsGrayJS() && !pi->IsBlackJS() && + pi->mInternalRefs != pi->mRefCount) { + mLogger->DescribeRoot((uint64_t)pi->mPointer, + pi->mInternalRefs); + } + break; + case white: + mLogger->DescribeGarbage((uint64_t)pi->mPointer); + break; + case grey: + MOZ_ASSERT(false, "All traversed objects should be black or white"); + break; + } + } + + mLogger->End(); + mLogger = nullptr; + timeLog.Checkpoint("ScanRoots::listener"); + } +} + + +//////////////////////////////////////////////////////////////////////// +// Bacon & Rajan's |CollectWhite| routine, somewhat modified. +//////////////////////////////////////////////////////////////////////// + +bool +nsCycleCollector::CollectWhite() +{ + // Explanation of "somewhat modified": we have no way to collect the + // set of whites "all at once", we have to ask each of them to drop + // their outgoing links and assume this will cause the garbage cycle + // to *mostly* self-destruct (except for the reference we continue + // to hold). + // + // To do this "safely" we must make sure that the white nodes we're + // operating on are stable for the duration of our operation. So we + // make 3 sets of calls to language runtimes: + // + // - Root(whites), which should pin the whites in memory. + // - Unlink(whites), which drops outgoing links on each white. + // - Unroot(whites), which returns the whites to normal GC. + + // Segments are 4 KiB on 32-bit and 8 KiB on 64-bit. + static const size_t kSegmentSize = sizeof(void*) * 1024; + SegmentedVector<PtrInfo*, kSegmentSize, InfallibleAllocPolicy> + whiteNodes(kSegmentSize); + TimeLog timeLog; + + MOZ_ASSERT(mIncrementalPhase == ScanAndCollectWhitePhase); + + uint32_t numWhiteNodes = 0; + uint32_t numWhiteGCed = 0; + uint32_t numWhiteJSZones = 0; + + { + JS::AutoAssertNoGC nogc; + bool hasJSContext = !!mJSContext; + nsCycleCollectionParticipant* zoneParticipant = + hasJSContext ? mJSContext->ZoneParticipant() : nullptr; + + NodePool::Enumerator etor(mGraph.mNodes); + while (!etor.IsDone()) { + PtrInfo* pinfo = etor.GetNext(); + if (pinfo->mColor == white && pinfo->mParticipant) { + if (pinfo->IsGrayJS()) { + MOZ_ASSERT(mJSContext); + ++numWhiteGCed; + JS::Zone* zone; + if (MOZ_UNLIKELY(pinfo->mParticipant == zoneParticipant)) { + ++numWhiteJSZones; + zone = static_cast<JS::Zone*>(pinfo->mPointer); + } else { + JS::GCCellPtr ptr(pinfo->mPointer, JS::GCThingTraceKind(pinfo->mPointer)); + zone = JS::GetTenuredGCThingZone(ptr); + } + mJSContext->AddZoneWaitingForGC(zone); + } else { + whiteNodes.InfallibleAppend(pinfo); + pinfo->mParticipant->Root(pinfo->mPointer); + ++numWhiteNodes; + } + } + } + } + + mResults.mFreedRefCounted += numWhiteNodes; + mResults.mFreedGCed += numWhiteGCed; + mResults.mFreedJSZones += numWhiteJSZones; + + timeLog.Checkpoint("CollectWhite::Root"); + + if (mBeforeUnlinkCB) { + mBeforeUnlinkCB(); + timeLog.Checkpoint("CollectWhite::BeforeUnlinkCB"); + } + + // Unlink() can trigger a GC, so do not touch any JS or anything + // else not in whiteNodes after here. + + for (auto iter = whiteNodes.Iter(); !iter.Done(); iter.Next()) { + PtrInfo* pinfo = iter.Get(); + MOZ_ASSERT(pinfo->mParticipant, + "Unlink shouldn't see objects removed from graph."); + pinfo->mParticipant->Unlink(pinfo->mPointer); +#ifdef DEBUG + if (mJSContext) { + mJSContext->AssertNoObjectsToTrace(pinfo->mPointer); + } +#endif + } + timeLog.Checkpoint("CollectWhite::Unlink"); + + JS::AutoAssertNoGC nogc; + for (auto iter = whiteNodes.Iter(); !iter.Done(); iter.Next()) { + PtrInfo* pinfo = iter.Get(); + MOZ_ASSERT(pinfo->mParticipant, + "Unroot shouldn't see objects removed from graph."); + pinfo->mParticipant->Unroot(pinfo->mPointer); + } + timeLog.Checkpoint("CollectWhite::Unroot"); + + nsCycleCollector_dispatchDeferredDeletion(false, true); + timeLog.Checkpoint("CollectWhite::dispatchDeferredDeletion"); + + mIncrementalPhase = CleanupPhase; + + return numWhiteNodes > 0 || numWhiteGCed > 0 || numWhiteJSZones > 0; +} + + +//////////////////////// +// Memory reporting +//////////////////////// + +MOZ_DEFINE_MALLOC_SIZE_OF(CycleCollectorMallocSizeOf) + +NS_IMETHODIMP +nsCycleCollector::CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) +{ + size_t objectSize, graphSize, purpleBufferSize; + SizeOfIncludingThis(CycleCollectorMallocSizeOf, + &objectSize, &graphSize, + &purpleBufferSize); + + if (objectSize > 0) { + MOZ_COLLECT_REPORT( + "explicit/cycle-collector/collector-object", KIND_HEAP, UNITS_BYTES, + objectSize, + "Memory used for the cycle collector object itself."); + } + + if (graphSize > 0) { + MOZ_COLLECT_REPORT( + "explicit/cycle-collector/graph", KIND_HEAP, UNITS_BYTES, + graphSize, + "Memory used for the cycle collector's graph. This should be zero when " + "the collector is idle."); + } + + if (purpleBufferSize > 0) { + MOZ_COLLECT_REPORT( + "explicit/cycle-collector/purple-buffer", KIND_HEAP, UNITS_BYTES, + purpleBufferSize, + "Memory used for the cycle collector's purple buffer."); + } + + return NS_OK; +}; + + +//////////////////////////////////////////////////////////////////////// +// Collector implementation +//////////////////////////////////////////////////////////////////////// + +nsCycleCollector::nsCycleCollector() : + mActivelyCollecting(false), + mFreeingSnowWhite(false), + mScanInProgress(false), + mJSContext(nullptr), + mIncrementalPhase(IdlePhase), +#ifdef DEBUG + mThread(NS_GetCurrentThread()), +#endif + mWhiteNodeCount(0), + mBeforeUnlinkCB(nullptr), + mForgetSkippableCB(nullptr), + mUnmergedNeeded(0), + mMergedInARow(0) +{ +} + +nsCycleCollector::~nsCycleCollector() +{ + UnregisterWeakMemoryReporter(this); +} + +void +nsCycleCollector::RegisterJSContext(CycleCollectedJSContext* aJSContext) +{ + MOZ_RELEASE_ASSERT(!mJSContext, "Multiple registrations of JS context in cycle collector"); + mJSContext = aJSContext; + + if (!NS_IsMainThread()) { + return; + } + + // We can't register as a reporter in nsCycleCollector() because that runs + // before the memory reporter manager is initialized. So we do it here + // instead. + RegisterWeakMemoryReporter(this); +} + +void +nsCycleCollector::ForgetJSContext() +{ + MOZ_RELEASE_ASSERT(mJSContext, "Forgetting JS context in cycle collector before a JS context was registered"); + mJSContext = nullptr; +} + +#ifdef DEBUG +static bool +HasParticipant(void* aPtr, nsCycleCollectionParticipant* aParti) +{ + if (aParti) { + return true; + } + + nsXPCOMCycleCollectionParticipant* xcp; + ToParticipant(static_cast<nsISupports*>(aPtr), &xcp); + return xcp != nullptr; +} +#endif + +MOZ_ALWAYS_INLINE void +nsCycleCollector::Suspect(void* aPtr, nsCycleCollectionParticipant* aParti, + nsCycleCollectingAutoRefCnt* aRefCnt) +{ + CheckThreadSafety(); + + // Don't call AddRef or Release of a CCed object in a Traverse() method. + MOZ_ASSERT(!mScanInProgress, "Attempted to call Suspect() while a scan was in progress"); + + if (MOZ_UNLIKELY(mScanInProgress)) { + return; + } + + MOZ_ASSERT(aPtr, "Don't suspect null pointers"); + + MOZ_ASSERT(HasParticipant(aPtr, aParti), + "Suspected nsISupports pointer must QI to nsXPCOMCycleCollectionParticipant"); + + mPurpleBuf.Put(aPtr, aParti, aRefCnt); +} + +void +nsCycleCollector::CheckThreadSafety() +{ +#ifdef DEBUG + nsIThread* currentThread = NS_GetCurrentThread(); + // XXXkhuey we can be called so late in shutdown that NS_GetCurrentThread + // returns null (after the thread manager has shut down) + MOZ_ASSERT(mThread == currentThread || !currentThread); +#endif +} + +// The cycle collector uses the mark bitmap to discover what JS objects +// were reachable only from XPConnect roots that might participate in +// cycles. We ask the JS context whether we need to force a GC before +// this CC. It returns true on startup (before the mark bits have been set), +// and also when UnmarkGray has run out of stack. We also force GCs on shut +// down to collect cycles involving both DOM and JS. +void +nsCycleCollector::FixGrayBits(bool aForceGC, TimeLog& aTimeLog) +{ + CheckThreadSafety(); + + if (!mJSContext) { + return; + } + + if (!aForceGC) { + mJSContext->FixWeakMappingGrayBits(); + aTimeLog.Checkpoint("FixWeakMappingGrayBits"); + + bool needGC = !mJSContext->AreGCGrayBitsValid(); + // Only do a telemetry ping for non-shutdown CCs. + CC_TELEMETRY(_NEED_GC, needGC); + if (!needGC) { + return; + } + mResults.mForcedGC = true; + } + + mJSContext->GarbageCollect(aForceGC ? JS::gcreason::SHUTDOWN_CC : + JS::gcreason::CC_FORCED); + aTimeLog.Checkpoint("FixGrayBits GC"); +} + +bool +nsCycleCollector::IsIncrementalGCInProgress() +{ + return mJSContext && JS::IsIncrementalGCInProgress(mJSContext->Context()); +} + +void +nsCycleCollector::FinishAnyIncrementalGCInProgress() +{ + if (IsIncrementalGCInProgress()) { + NS_WARNING("Finishing incremental GC in progress during CC"); + JS::PrepareForIncrementalGC(mJSContext->Context()); + JS::FinishIncrementalGC(mJSContext->Context(), JS::gcreason::CC_FORCED); + } +} + +void +nsCycleCollector::CleanupAfterCollection() +{ + TimeLog timeLog; + MOZ_ASSERT(mIncrementalPhase == CleanupPhase); + mGraph.Clear(); + timeLog.Checkpoint("CleanupAfterCollection::mGraph.Clear()"); + + uint32_t interval = + (uint32_t)((TimeStamp::Now() - mCollectionStart).ToMilliseconds()); +#ifdef COLLECT_TIME_DEBUG + printf("cc: total cycle collector time was %ums in %u slices\n", interval, + mResults.mNumSlices); + printf("cc: visited %u ref counted and %u GCed objects, freed %d ref counted and %d GCed objects", + mResults.mVisitedRefCounted, mResults.mVisitedGCed, + mResults.mFreedRefCounted, mResults.mFreedGCed); + uint32_t numVisited = mResults.mVisitedRefCounted + mResults.mVisitedGCed; + if (numVisited > 1000) { + uint32_t numFreed = mResults.mFreedRefCounted + mResults.mFreedGCed; + printf(" (%d%%)", 100 * numFreed / numVisited); + } + printf(".\ncc: \n"); +#endif + + CC_TELEMETRY( , interval); + CC_TELEMETRY(_VISITED_REF_COUNTED, mResults.mVisitedRefCounted); + CC_TELEMETRY(_VISITED_GCED, mResults.mVisitedGCed); + CC_TELEMETRY(_COLLECTED, mWhiteNodeCount); + timeLog.Checkpoint("CleanupAfterCollection::telemetry"); + + if (mJSContext) { + mJSContext->FinalizeDeferredThings(mResults.mAnyManual + ? CycleCollectedJSContext::FinalizeNow + : CycleCollectedJSContext::FinalizeIncrementally); + mJSContext->EndCycleCollectionCallback(mResults); + timeLog.Checkpoint("CleanupAfterCollection::EndCycleCollectionCallback()"); + } + mIncrementalPhase = IdlePhase; +} + +void +nsCycleCollector::ShutdownCollect() +{ + FinishAnyIncrementalGCInProgress(); + + SliceBudget unlimitedBudget = SliceBudget::unlimited(); + uint32_t i; + for (i = 0; i < DEFAULT_SHUTDOWN_COLLECTIONS; ++i) { + if (!Collect(ShutdownCC, unlimitedBudget, nullptr)) { + break; + } + } + NS_WARNING_ASSERTION(i < NORMAL_SHUTDOWN_COLLECTIONS, "Extra shutdown CC"); +} + +static void +PrintPhase(const char* aPhase) +{ +#ifdef DEBUG_PHASES + printf("cc: begin %s on %s\n", aPhase, + NS_IsMainThread() ? "mainthread" : "worker"); +#endif +} + +bool +nsCycleCollector::Collect(ccType aCCType, + SliceBudget& aBudget, + nsICycleCollectorListener* aManualListener, + bool aPreferShorterSlices) +{ + CheckThreadSafety(); + + // This can legitimately happen in a few cases. See bug 383651. + if (mActivelyCollecting || mFreeingSnowWhite) { + return false; + } + mActivelyCollecting = true; + + MOZ_ASSERT(!IsIncrementalGCInProgress()); + + mozilla::Maybe<mozilla::AutoGlobalTimelineMarker> marker; + if (NS_IsMainThread()) { + marker.emplace("nsCycleCollector::Collect", MarkerStackRequest::NO_STACK); + } + + bool startedIdle = IsIdle(); + bool collectedAny = false; + + // If the CC started idle, it will call BeginCollection, which + // will do FreeSnowWhite, so it doesn't need to be done here. + if (!startedIdle) { + TimeLog timeLog; + FreeSnowWhite(true); + timeLog.Checkpoint("Collect::FreeSnowWhite"); + } + + if (aCCType != SliceCC) { + mResults.mAnyManual = true; + } + + ++mResults.mNumSlices; + + bool continueSlice = aBudget.isUnlimited() || !aPreferShorterSlices; + do { + switch (mIncrementalPhase) { + case IdlePhase: + PrintPhase("BeginCollection"); + BeginCollection(aCCType, aManualListener); + break; + case GraphBuildingPhase: + PrintPhase("MarkRoots"); + MarkRoots(aBudget); + + // Only continue this slice if we're running synchronously or the + // next phase will probably be short, to reduce the max pause for this + // collection. + // (There's no need to check if we've finished graph building, because + // if we haven't, we've already exceeded our budget, and will finish + // this slice anyways.) + continueSlice = aBudget.isUnlimited() || + (mResults.mNumSlices < 3 && !aPreferShorterSlices); + break; + case ScanAndCollectWhitePhase: + // We do ScanRoots and CollectWhite in a single slice to ensure + // that we won't unlink a live object if a weak reference is + // promoted to a strong reference after ScanRoots has finished. + // See bug 926533. + PrintPhase("ScanRoots"); + ScanRoots(startedIdle); + PrintPhase("CollectWhite"); + collectedAny = CollectWhite(); + break; + case CleanupPhase: + PrintPhase("CleanupAfterCollection"); + CleanupAfterCollection(); + continueSlice = false; + break; + } + if (continueSlice) { + // Force SliceBudget::isOverBudget to check the time. + aBudget.step(SliceBudget::CounterReset); + continueSlice = !aBudget.isOverBudget(); + } + } while (continueSlice); + + // Clear mActivelyCollecting here to ensure that a recursive call to + // Collect() does something. + mActivelyCollecting = false; + + if (aCCType != SliceCC && !startedIdle) { + // We were in the middle of an incremental CC (using its own listener). + // Somebody has forced a CC, so after having finished out the current CC, + // run the CC again using the new listener. + MOZ_ASSERT(IsIdle()); + if (Collect(aCCType, aBudget, aManualListener)) { + collectedAny = true; + } + } + + MOZ_ASSERT_IF(aCCType != SliceCC, IsIdle()); + + return collectedAny; +} + +// Any JS objects we have in the graph could die when we GC, but we +// don't want to abandon the current CC, because the graph contains +// information about purple roots. So we synchronously finish off +// the current CC. +void +nsCycleCollector::PrepareForGarbageCollection() +{ + if (IsIdle()) { + MOZ_ASSERT(mGraph.IsEmpty(), "Non-empty graph when idle"); + MOZ_ASSERT(!mBuilder, "Non-null builder when idle"); + if (mJSPurpleBuffer) { + mJSPurpleBuffer->Destroy(); + } + return; + } + + FinishAnyCurrentCollection(); +} + +void +nsCycleCollector::FinishAnyCurrentCollection() +{ + if (IsIdle()) { + return; + } + + SliceBudget unlimitedBudget = SliceBudget::unlimited(); + PrintPhase("FinishAnyCurrentCollection"); + // Use SliceCC because we only want to finish the CC in progress. + Collect(SliceCC, unlimitedBudget, nullptr); + + // It is only okay for Collect() to have failed to finish the + // current CC if we're reentering the CC at some point past + // graph building. We need to be past the point where the CC will + // look at JS objects so that it is safe to GC. + MOZ_ASSERT(IsIdle() || + (mActivelyCollecting && mIncrementalPhase != GraphBuildingPhase), + "Reentered CC during graph building"); +} + +// Don't merge too many times in a row, and do at least a minimum +// number of unmerged CCs in a row. +static const uint32_t kMinConsecutiveUnmerged = 3; +static const uint32_t kMaxConsecutiveMerged = 3; + +bool +nsCycleCollector::ShouldMergeZones(ccType aCCType) +{ + if (!mJSContext) { + return false; + } + + MOZ_ASSERT(mUnmergedNeeded <= kMinConsecutiveUnmerged); + MOZ_ASSERT(mMergedInARow <= kMaxConsecutiveMerged); + + if (mMergedInARow == kMaxConsecutiveMerged) { + MOZ_ASSERT(mUnmergedNeeded == 0); + mUnmergedNeeded = kMinConsecutiveUnmerged; + } + + if (mUnmergedNeeded > 0) { + mUnmergedNeeded--; + mMergedInARow = 0; + return false; + } + + if (aCCType == SliceCC && mJSContext->UsefulToMergeZones()) { + mMergedInARow++; + return true; + } else { + mMergedInARow = 0; + return false; + } +} + +void +nsCycleCollector::BeginCollection(ccType aCCType, + nsICycleCollectorListener* aManualListener) +{ + TimeLog timeLog; + MOZ_ASSERT(IsIdle()); + + mCollectionStart = TimeStamp::Now(); + + if (mJSContext) { + mJSContext->BeginCycleCollectionCallback(); + timeLog.Checkpoint("BeginCycleCollectionCallback()"); + } + + bool isShutdown = (aCCType == ShutdownCC); + + // Set up the listener for this CC. + MOZ_ASSERT_IF(isShutdown, !aManualListener); + MOZ_ASSERT(!mLogger, "Forgot to clear a previous listener?"); + + if (aManualListener) { + aManualListener->AsLogger(getter_AddRefs(mLogger)); + } + + aManualListener = nullptr; + if (!mLogger && mParams.LogThisCC(isShutdown)) { + mLogger = new nsCycleCollectorLogger(); + if (mParams.AllTracesThisCC(isShutdown)) { + mLogger->SetAllTraces(); + } + } + + // On a WantAllTraces CC, force a synchronous global GC to prevent + // hijinks from ForgetSkippable and compartmental GCs. + bool forceGC = isShutdown || (mLogger && mLogger->IsAllTraces()); + + // BeginCycleCollectionCallback() might have started an IGC, and we need + // to finish it before we run FixGrayBits. + FinishAnyIncrementalGCInProgress(); + timeLog.Checkpoint("Pre-FixGrayBits finish IGC"); + + FixGrayBits(forceGC, timeLog); + + FreeSnowWhite(true); + timeLog.Checkpoint("BeginCollection FreeSnowWhite"); + + if (mLogger && NS_FAILED(mLogger->Begin())) { + mLogger = nullptr; + } + + // FreeSnowWhite could potentially have started an IGC, which we need + // to finish before we look at any JS roots. + FinishAnyIncrementalGCInProgress(); + timeLog.Checkpoint("Post-FreeSnowWhite finish IGC"); + + // Set up the data structures for building the graph. + JS::AutoAssertNoGC nogc; + JS::AutoEnterCycleCollection autocc(mJSContext->Context()); + mGraph.Init(); + mResults.Init(); + mResults.mAnyManual = (aCCType != SliceCC); + bool mergeZones = ShouldMergeZones(aCCType); + mResults.mMergedZones = mergeZones; + + MOZ_ASSERT(!mBuilder, "Forgot to clear mBuilder"); + mBuilder = new CCGraphBuilder(mGraph, mResults, mJSContext, mLogger, + mergeZones); + timeLog.Checkpoint("BeginCollection prepare graph builder"); + + if (mJSContext) { + mJSContext->TraverseRoots(*mBuilder); + timeLog.Checkpoint("mJSContext->TraverseRoots()"); + } + + AutoRestore<bool> ar(mScanInProgress); + MOZ_RELEASE_ASSERT(!mScanInProgress); + mScanInProgress = true; + mPurpleBuf.SelectPointers(*mBuilder); + timeLog.Checkpoint("SelectPointers()"); + + mBuilder->DoneAddingRoots(); + mIncrementalPhase = GraphBuildingPhase; +} + +uint32_t +nsCycleCollector::SuspectedCount() +{ + CheckThreadSafety(); + return mPurpleBuf.Count(); +} + +void +nsCycleCollector::Shutdown(bool aDoCollect) +{ + CheckThreadSafety(); + + // Always delete snow white objects. + FreeSnowWhite(true); + + if (aDoCollect) { + ShutdownCollect(); + } +} + +void +nsCycleCollector::RemoveObjectFromGraph(void* aObj) +{ + if (IsIdle()) { + return; + } + + mGraph.RemoveObjectFromMap(aObj); +} + +void +nsCycleCollector::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf, + size_t* aObjectSize, + size_t* aGraphSize, + size_t* aPurpleBufferSize) const +{ + *aObjectSize = aMallocSizeOf(this); + + *aGraphSize = mGraph.SizeOfExcludingThis(aMallocSizeOf); + + *aPurpleBufferSize = mPurpleBuf.SizeOfExcludingThis(aMallocSizeOf); + + // These fields are deliberately not measured: + // - mJSContext: because it's non-owning and measured by JS reporters. + // - mParams: because it only contains scalars. +} + +JSPurpleBuffer* +nsCycleCollector::GetJSPurpleBuffer() +{ + if (!mJSPurpleBuffer) { + // The Release call here confuses the GC analysis. + JS::AutoSuppressGCAnalysis nogc; + // JSPurpleBuffer keeps itself alive, but we need to create it in such way + // that it ends up in the normal purple buffer. That happens when + // nsRefPtr goes out of the scope and calls Release. + RefPtr<JSPurpleBuffer> pb = new JSPurpleBuffer(mJSPurpleBuffer); + } + return mJSPurpleBuffer; +} + +//////////////////////////////////////////////////////////////////////// +// Module public API (exported in nsCycleCollector.h) +// Just functions that redirect into the singleton, once it's built. +//////////////////////////////////////////////////////////////////////// + +void +nsCycleCollector_registerJSContext(CycleCollectedJSContext* aCx) +{ + CollectorData* data = sCollectorData.get(); + + // We should have started the cycle collector by now. + MOZ_ASSERT(data); + MOZ_ASSERT(data->mCollector); + // But we shouldn't already have a context. + MOZ_ASSERT(!data->mContext); + + data->mContext = aCx; + data->mCollector->RegisterJSContext(aCx); +} + +void +nsCycleCollector_forgetJSContext() +{ + CollectorData* data = sCollectorData.get(); + + // We should have started the cycle collector by now. + MOZ_ASSERT(data); + // And we shouldn't have already forgotten our context. + MOZ_ASSERT(data->mContext); + + // But it may have shutdown already. + if (data->mCollector) { + data->mCollector->ForgetJSContext(); + data->mContext = nullptr; + } else { + data->mContext = nullptr; + delete data; + sCollectorData.set(nullptr); + } +} + +/* static */ CycleCollectedJSContext* +CycleCollectedJSContext::Get() +{ + CollectorData* data = sCollectorData.get(); + if (data) { + return data->mContext; + } + return nullptr; +} + +MOZ_NEVER_INLINE static void +SuspectAfterShutdown(void* aPtr, nsCycleCollectionParticipant* aCp, + nsCycleCollectingAutoRefCnt* aRefCnt, + bool* aShouldDelete) +{ + if (aRefCnt->get() == 0) { + if (!aShouldDelete) { + // The CC is shut down, so we can't be in the middle of an ICC. + CanonicalizeParticipant(&aPtr, &aCp); + aRefCnt->stabilizeForDeletion(); + aCp->DeleteCycleCollectable(aPtr); + } else { + *aShouldDelete = true; + } + } else { + // Make sure we'll get called again. + aRefCnt->RemoveFromPurpleBuffer(); + } +} + +void +NS_CycleCollectorSuspect3(void* aPtr, nsCycleCollectionParticipant* aCp, + nsCycleCollectingAutoRefCnt* aRefCnt, + bool* aShouldDelete) +{ + CollectorData* data = sCollectorData.get(); + + // We should have started the cycle collector by now. + MOZ_ASSERT(data); + + if (MOZ_LIKELY(data->mCollector)) { + data->mCollector->Suspect(aPtr, aCp, aRefCnt); + return; + } + SuspectAfterShutdown(aPtr, aCp, aRefCnt, aShouldDelete); +} + +uint32_t +nsCycleCollector_suspectedCount() +{ + CollectorData* data = sCollectorData.get(); + + // We should have started the cycle collector by now. + MOZ_ASSERT(data); + + if (!data->mCollector) { + return 0; + } + + return data->mCollector->SuspectedCount(); +} + +bool +nsCycleCollector_init() +{ +#ifdef DEBUG + static bool sInitialized; + + MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); + MOZ_ASSERT(!sInitialized, "Called twice!?"); + sInitialized = true; +#endif + + return sCollectorData.init(); +} + +void +nsCycleCollector_startup() +{ + if (sCollectorData.get()) { + MOZ_CRASH(); + } + + CollectorData* data = new CollectorData; + data->mCollector = new nsCycleCollector(); + data->mContext = nullptr; + + sCollectorData.set(data); +} + +void +nsCycleCollector_setBeforeUnlinkCallback(CC_BeforeUnlinkCallback aCB) +{ + CollectorData* data = sCollectorData.get(); + + // We should have started the cycle collector by now. + MOZ_ASSERT(data); + MOZ_ASSERT(data->mCollector); + + data->mCollector->SetBeforeUnlinkCallback(aCB); +} + +void +nsCycleCollector_setForgetSkippableCallback(CC_ForgetSkippableCallback aCB) +{ + CollectorData* data = sCollectorData.get(); + + // We should have started the cycle collector by now. + MOZ_ASSERT(data); + MOZ_ASSERT(data->mCollector); + + data->mCollector->SetForgetSkippableCallback(aCB); +} + +void +nsCycleCollector_forgetSkippable(bool aRemoveChildlessNodes, + bool aAsyncSnowWhiteFreeing) +{ + CollectorData* data = sCollectorData.get(); + + // We should have started the cycle collector by now. + MOZ_ASSERT(data); + MOZ_ASSERT(data->mCollector); + + PROFILER_LABEL("nsCycleCollector", "forgetSkippable", + js::ProfileEntry::Category::CC); + + TimeLog timeLog; + data->mCollector->ForgetSkippable(aRemoveChildlessNodes, + aAsyncSnowWhiteFreeing); + timeLog.Checkpoint("ForgetSkippable()"); +} + +void +nsCycleCollector_dispatchDeferredDeletion(bool aContinuation, bool aPurge) +{ + CycleCollectedJSContext* cx = CycleCollectedJSContext::Get(); + if (cx) { + cx->DispatchDeferredDeletion(aContinuation, aPurge); + } +} + +bool +nsCycleCollector_doDeferredDeletion() +{ + CollectorData* data = sCollectorData.get(); + + // We should have started the cycle collector by now. + MOZ_ASSERT(data); + MOZ_ASSERT(data->mCollector); + MOZ_ASSERT(data->mContext); + + return data->mCollector->FreeSnowWhite(false); +} + +already_AddRefed<nsICycleCollectorLogSink> +nsCycleCollector_createLogSink() +{ + nsCOMPtr<nsICycleCollectorLogSink> sink = new nsCycleCollectorLogSinkToFile(); + return sink.forget(); +} + +void +nsCycleCollector_collect(nsICycleCollectorListener* aManualListener) +{ + CollectorData* data = sCollectorData.get(); + + // We should have started the cycle collector by now. + MOZ_ASSERT(data); + MOZ_ASSERT(data->mCollector); + + PROFILER_LABEL("nsCycleCollector", "collect", + js::ProfileEntry::Category::CC); + + SliceBudget unlimitedBudget = SliceBudget::unlimited(); + data->mCollector->Collect(ManualCC, unlimitedBudget, aManualListener); +} + +void +nsCycleCollector_collectSlice(SliceBudget& budget, + bool aPreferShorterSlices) +{ + CollectorData* data = sCollectorData.get(); + + // We should have started the cycle collector by now. + MOZ_ASSERT(data); + MOZ_ASSERT(data->mCollector); + + PROFILER_LABEL("nsCycleCollector", "collectSlice", + js::ProfileEntry::Category::CC); + + data->mCollector->Collect(SliceCC, budget, nullptr, aPreferShorterSlices); +} + +void +nsCycleCollector_prepareForGarbageCollection() +{ + CollectorData* data = sCollectorData.get(); + + MOZ_ASSERT(data); + + if (!data->mCollector) { + return; + } + + data->mCollector->PrepareForGarbageCollection(); +} + +void +nsCycleCollector_finishAnyCurrentCollection() +{ + CollectorData* data = sCollectorData.get(); + + MOZ_ASSERT(data); + + if (!data->mCollector) { + return; + } + + data->mCollector->FinishAnyCurrentCollection(); +} + +void +nsCycleCollector_shutdown(bool aDoCollect) +{ + CollectorData* data = sCollectorData.get(); + + if (data) { + MOZ_ASSERT(data->mCollector); + PROFILER_LABEL("nsCycleCollector", "shutdown", + js::ProfileEntry::Category::CC); + + data->mCollector->Shutdown(aDoCollect); + data->mCollector = nullptr; + if (data->mContext) { + // Run any remaining tasks that may have been enqueued via + // RunInStableState during the final cycle collection. + data->mContext->ProcessStableStateQueue(); + } + if (!data->mContext) { + delete data; + sCollectorData.set(nullptr); + } + } +} diff --git a/xpcom/base/nsCycleCollector.h b/xpcom/base/nsCycleCollector.h new file mode 100644 index 000000000..cd3fff406 --- /dev/null +++ b/xpcom/base/nsCycleCollector.h @@ -0,0 +1,72 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsCycleCollector_h__ +#define nsCycleCollector_h__ + +class nsICycleCollectorListener; +class nsICycleCollectorLogSink; +class nsISupports; +template<class T> struct already_AddRefed; + +#include "nsError.h" +#include "nsID.h" + +#include "js/SliceBudget.h" + +namespace mozilla { +class CycleCollectedJSContext; +} // namespace mozilla + +bool nsCycleCollector_init(); + +void nsCycleCollector_startup(); + +typedef void (*CC_BeforeUnlinkCallback)(void); +void nsCycleCollector_setBeforeUnlinkCallback(CC_BeforeUnlinkCallback aCB); + +typedef void (*CC_ForgetSkippableCallback)(void); +void nsCycleCollector_setForgetSkippableCallback(CC_ForgetSkippableCallback aCB); + +void nsCycleCollector_forgetSkippable(bool aRemoveChildlessNodes = false, + bool aAsyncSnowWhiteFreeing = false); + +void nsCycleCollector_prepareForGarbageCollection(); + +// If an incremental cycle collection is in progress, finish it. +void nsCycleCollector_finishAnyCurrentCollection(); + +void nsCycleCollector_dispatchDeferredDeletion(bool aContinuation = false, + bool aPurge = false); +bool nsCycleCollector_doDeferredDeletion(); + +already_AddRefed<nsICycleCollectorLogSink> nsCycleCollector_createLogSink(); + +void nsCycleCollector_collect(nsICycleCollectorListener* aManualListener); + +void nsCycleCollector_collectSlice(js::SliceBudget& budget, + bool aPreferShorterSlices = false); + +uint32_t nsCycleCollector_suspectedCount(); + +// If aDoCollect is true, then run the GC and CC a few times before +// shutting down the CC completely. +void nsCycleCollector_shutdown(bool aDoCollect = true); + +// Helpers for interacting with JS +void nsCycleCollector_registerJSContext(mozilla::CycleCollectedJSContext* aCx); +void nsCycleCollector_forgetJSContext(); + +#define NS_CYCLE_COLLECTOR_LOGGER_CID \ +{ 0x58be81b4, 0x39d2, 0x437c, \ +{ 0x94, 0xea, 0xae, 0xde, 0x2c, 0x62, 0x08, 0xd3 } } + +extern nsresult +nsCycleCollectorLoggerConstructor(nsISupports* aOuter, + const nsIID& aIID, + void** aInstancePtr); + +#endif // nsCycleCollector_h__ diff --git a/xpcom/base/nsCycleCollectorTraceJSHelpers.cpp b/xpcom/base/nsCycleCollectorTraceJSHelpers.cpp new file mode 100644 index 000000000..eb06a389c --- /dev/null +++ b/xpcom/base/nsCycleCollectorTraceJSHelpers.cpp @@ -0,0 +1,105 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsCycleCollectionParticipant.h" +#include "jsapi.h" +#include "jsfriendapi.h" + +void +CycleCollectionNoteEdgeNameImpl(nsCycleCollectionTraversalCallback& aCallback, + const char* aName, + uint32_t aFlags) +{ + nsAutoCString arrayEdgeName(aName); + if (aFlags & CycleCollectionEdgeNameArrayFlag) { + arrayEdgeName.AppendLiteral("[i]"); + } + aCallback.NoteNextEdgeName(arrayEdgeName.get()); +} + +void +nsScriptObjectTracer::NoteJSChild(JS::GCCellPtr aGCThing, const char* aName, + void* aClosure) +{ + nsCycleCollectionTraversalCallback* cb = + static_cast<nsCycleCollectionTraversalCallback*>(aClosure); + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*cb, aName); + if (mozilla::AddToCCKind(aGCThing.kind())) { + cb->NoteJSChild(aGCThing); + } +} + +void +TraceCallbackFunc::Trace(JS::Heap<JS::Value>* aPtr, const char* aName, + void* aClosure) const +{ + if (aPtr->unbarrieredGet().isMarkable()) { + mCallback(JS::GCCellPtr(aPtr->unbarrieredGet()), aName, aClosure); + } +} + +void +TraceCallbackFunc::Trace(JS::Heap<jsid>* aPtr, const char* aName, + void* aClosure) const +{ + if (JSID_IS_GCTHING(aPtr->unbarrieredGet())) { + mCallback(JSID_TO_GCTHING(aPtr->unbarrieredGet()), aName, aClosure); + } +} + +void +TraceCallbackFunc::Trace(JS::Heap<JSObject*>* aPtr, const char* aName, + void* aClosure) const +{ + if (*aPtr) { + mCallback(JS::GCCellPtr(aPtr->unbarrieredGet()), aName, aClosure); + } +} + +void +TraceCallbackFunc::Trace(JSObject** aPtr, const char* aName, + void* aClosure) const +{ + if (*aPtr) { + mCallback(JS::GCCellPtr(*aPtr), aName, aClosure); + } +} + +void +TraceCallbackFunc::Trace(JS::TenuredHeap<JSObject*>* aPtr, const char* aName, + void* aClosure) const +{ + if (*aPtr) { + mCallback(JS::GCCellPtr(aPtr->unbarrieredGetPtr()), aName, aClosure); + } +} + +void +TraceCallbackFunc::Trace(JS::Heap<JSFunction*>* aPtr, const char* aName, + void* aClosure) const +{ + if (*aPtr) { + mCallback(JS::GCCellPtr(aPtr->unbarrieredGet()), aName, aClosure); + } +} + +void +TraceCallbackFunc::Trace(JS::Heap<JSString*>* aPtr, const char* aName, + void* aClosure) const +{ + if (*aPtr) { + mCallback(JS::GCCellPtr(aPtr->unbarrieredGet()), aName, aClosure); + } +} + +void +TraceCallbackFunc::Trace(JS::Heap<JSScript*>* aPtr, const char* aName, + void* aClosure) const +{ + if (*aPtr) { + mCallback(JS::GCCellPtr(aPtr->unbarrieredGet()), aName, aClosure); + } +} diff --git a/xpcom/base/nsDebugImpl.cpp b/xpcom/base/nsDebugImpl.cpp new file mode 100644 index 000000000..36288d203 --- /dev/null +++ b/xpcom/base/nsDebugImpl.cpp @@ -0,0 +1,607 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Chromium headers must come before Mozilla headers. +#include "base/process_util.h" + +#include "mozilla/Atomics.h" + +#include "nsDebugImpl.h" +#include "nsDebug.h" +#ifdef MOZ_CRASHREPORTER +# include "nsExceptionHandler.h" +#endif +#include "nsString.h" +#include "nsXULAppAPI.h" +#include "prprf.h" +#include "nsError.h" +#include "prerror.h" +#include "prerr.h" +#include "prenv.h" + +#ifdef ANDROID +#include <android/log.h> +#endif + +#ifdef _WIN32 +/* for getenv() */ +#include <stdlib.h> +#endif + +#include "nsTraceRefcnt.h" + +#if defined(XP_UNIX) +#include <signal.h> +#endif + +#if defined(XP_WIN) +#include <tchar.h> +#include "nsString.h" +#endif + +#if defined(XP_MACOSX) || defined(__DragonFly__) || defined(__FreeBSD__) \ + || defined(__NetBSD__) || defined(__OpenBSD__) +#include <stdbool.h> +#include <unistd.h> +#include <sys/param.h> +#include <sys/sysctl.h> +#endif + +#if defined(__OpenBSD__) +#include <sys/proc.h> +#endif + +#if defined(__DragonFly__) || defined(__FreeBSD__) +#include <sys/user.h> +#endif + +#if defined(__NetBSD__) +#undef KERN_PROC +#define KERN_PROC KERN_PROC2 +#define KINFO_PROC struct kinfo_proc2 +#else +#define KINFO_PROC struct kinfo_proc +#endif + +#if defined(XP_MACOSX) +#define KP_FLAGS kp_proc.p_flag +#elif defined(__DragonFly__) +#define KP_FLAGS kp_flags +#elif defined(__FreeBSD__) +#define KP_FLAGS ki_flag +#elif defined(__OpenBSD__) && !defined(_P_TRACED) +#define KP_FLAGS p_psflags +#define P_TRACED PS_TRACED +#else +#define KP_FLAGS p_flag +#endif + +#include "mozilla/mozalloc_abort.h" + +static void +Abort(const char* aMsg); + +static void +RealBreak(); + +static void +Break(const char* aMsg); + +#if defined(_WIN32) +#include <windows.h> +#include <signal.h> +#include <malloc.h> // for _alloca +#elif defined(XP_UNIX) +#include <stdlib.h> +#endif + +using namespace mozilla; + +static const char* sMultiprocessDescription = nullptr; + +static Atomic<int32_t> gAssertionCount; + +NS_IMPL_QUERY_INTERFACE(nsDebugImpl, nsIDebug2) + +NS_IMETHODIMP_(MozExternalRefCountType) +nsDebugImpl::AddRef() +{ + return 2; +} + +NS_IMETHODIMP_(MozExternalRefCountType) +nsDebugImpl::Release() +{ + return 1; +} + +NS_IMETHODIMP +nsDebugImpl::Assertion(const char* aStr, const char* aExpr, + const char* aFile, int32_t aLine) +{ + NS_DebugBreak(NS_DEBUG_ASSERTION, aStr, aExpr, aFile, aLine); + return NS_OK; +} + +NS_IMETHODIMP +nsDebugImpl::Warning(const char* aStr, const char* aFile, int32_t aLine) +{ + NS_DebugBreak(NS_DEBUG_WARNING, aStr, nullptr, aFile, aLine); + return NS_OK; +} + +NS_IMETHODIMP +nsDebugImpl::Break(const char* aFile, int32_t aLine) +{ + NS_DebugBreak(NS_DEBUG_BREAK, nullptr, nullptr, aFile, aLine); + return NS_OK; +} + +NS_IMETHODIMP +nsDebugImpl::Abort(const char* aFile, int32_t aLine) +{ + NS_DebugBreak(NS_DEBUG_ABORT, nullptr, nullptr, aFile, aLine); + return NS_OK; +} + +NS_IMETHODIMP +nsDebugImpl::GetIsDebugBuild(bool* aResult) +{ +#ifdef DEBUG + *aResult = true; +#else + *aResult = false; +#endif + return NS_OK; +} + +NS_IMETHODIMP +nsDebugImpl::GetAssertionCount(int32_t* aResult) +{ + *aResult = gAssertionCount; + return NS_OK; +} + +NS_IMETHODIMP +nsDebugImpl::GetIsDebuggerAttached(bool* aResult) +{ + *aResult = false; + +#if defined(XP_WIN) + *aResult = ::IsDebuggerPresent(); +#elif defined(XP_MACOSX) || defined(__DragonFly__) || defined(__FreeBSD__) \ + || defined(__NetBSD__) || defined(__OpenBSD__) + // Specify the info we're looking for + int mib[] = { + CTL_KERN, + KERN_PROC, + KERN_PROC_PID, + getpid(), +#if defined(__NetBSD__) || defined(__OpenBSD__) + sizeof(KINFO_PROC), + 1, +#endif + }; + u_int mibSize = sizeof(mib) / sizeof(int); + + KINFO_PROC info; + size_t infoSize = sizeof(info); + memset(&info, 0, infoSize); + + if (sysctl(mib, mibSize, &info, &infoSize, nullptr, 0)) { + // if the call fails, default to false + *aResult = false; + return NS_OK; + } + + if (info.KP_FLAGS & P_TRACED) { + *aResult = true; + } +#endif + + return NS_OK; +} + +/* static */ void +nsDebugImpl::SetMultiprocessMode(const char* aDesc) +{ + sMultiprocessDescription = aDesc; +} + +/** + * Implementation of the nsDebug methods. Note that this code is + * always compiled in, in case some other module that uses it is + * compiled with debugging even if this library is not. + */ +enum nsAssertBehavior +{ + NS_ASSERT_UNINITIALIZED, + NS_ASSERT_WARN, + NS_ASSERT_SUSPEND, + NS_ASSERT_STACK, + NS_ASSERT_TRAP, + NS_ASSERT_ABORT, + NS_ASSERT_STACK_AND_ABORT +}; + +static nsAssertBehavior +GetAssertBehavior() +{ + static nsAssertBehavior gAssertBehavior = NS_ASSERT_UNINITIALIZED; + if (gAssertBehavior != NS_ASSERT_UNINITIALIZED) { + return gAssertBehavior; + } + + gAssertBehavior = NS_ASSERT_WARN; + + const char* assertString = PR_GetEnv("XPCOM_DEBUG_BREAK"); + if (!assertString || !*assertString) { + return gAssertBehavior; + } + if (!strcmp(assertString, "warn")) { + return gAssertBehavior = NS_ASSERT_WARN; + } + if (!strcmp(assertString, "suspend")) { + return gAssertBehavior = NS_ASSERT_SUSPEND; + } + if (!strcmp(assertString, "stack")) { + return gAssertBehavior = NS_ASSERT_STACK; + } + if (!strcmp(assertString, "abort")) { + return gAssertBehavior = NS_ASSERT_ABORT; + } + if (!strcmp(assertString, "trap") || !strcmp(assertString, "break")) { + return gAssertBehavior = NS_ASSERT_TRAP; + } + if (!strcmp(assertString, "stack-and-abort")) { + return gAssertBehavior = NS_ASSERT_STACK_AND_ABORT; + } + + fprintf(stderr, "Unrecognized value of XPCOM_DEBUG_BREAK\n"); + return gAssertBehavior; +} + +struct FixedBuffer +{ + FixedBuffer() : curlen(0) + { + buffer[0] = '\0'; + } + + char buffer[500]; + uint32_t curlen; +}; + +static int +StuffFixedBuffer(void* aClosure, const char* aBuf, uint32_t aLen) +{ + if (!aLen) { + return 0; + } + + FixedBuffer* fb = (FixedBuffer*)aClosure; + + // strip the trailing null, we add it again later + if (aBuf[aLen - 1] == '\0') { + --aLen; + } + + if (fb->curlen + aLen >= sizeof(fb->buffer)) { + aLen = sizeof(fb->buffer) - fb->curlen - 1; + } + + if (aLen) { + memcpy(fb->buffer + fb->curlen, aBuf, aLen); + fb->curlen += aLen; + fb->buffer[fb->curlen] = '\0'; + } + + return aLen; +} + +EXPORT_XPCOM_API(void) +NS_DebugBreak(uint32_t aSeverity, const char* aStr, const char* aExpr, + const char* aFile, int32_t aLine) +{ + FixedBuffer nonPIDBuf; + FixedBuffer buf; + const char* sevString = "WARNING"; + + switch (aSeverity) { + case NS_DEBUG_ASSERTION: + sevString = "###!!! ASSERTION"; + break; + + case NS_DEBUG_BREAK: + sevString = "###!!! BREAK"; + break; + + case NS_DEBUG_ABORT: + sevString = "###!!! ABORT"; + break; + + default: + aSeverity = NS_DEBUG_WARNING; + } + +#define PRINT_TO_NONPID_BUFFER(...) PR_sxprintf(StuffFixedBuffer, &nonPIDBuf, __VA_ARGS__) + PRINT_TO_NONPID_BUFFER("%s: ", sevString); + if (aStr) { + PRINT_TO_NONPID_BUFFER("%s: ", aStr); + } + if (aExpr) { + PRINT_TO_NONPID_BUFFER("'%s', ", aExpr); + } + if (aFile) { + PRINT_TO_NONPID_BUFFER("file %s, ", aFile); + } + if (aLine != -1) { + PRINT_TO_NONPID_BUFFER("line %d", aLine); + } +#undef PRINT_TO_NONPID_BUFFER + + // Print "[PID]" or "[Desc PID]" at the beginning of the message. +#define PRINT_TO_BUFFER(...) PR_sxprintf(StuffFixedBuffer, &buf, __VA_ARGS__) + PRINT_TO_BUFFER("["); + if (sMultiprocessDescription) { + PRINT_TO_BUFFER("%s ", sMultiprocessDescription); + } + PRINT_TO_BUFFER("%d] %s", base::GetCurrentProcId(), nonPIDBuf.buffer); +#undef PRINT_TO_BUFFER + + + // errors on platforms without a debugdlg ring a bell on stderr +#if !defined(XP_WIN) + if (aSeverity != NS_DEBUG_WARNING) { + fprintf(stderr, "\07"); + } +#endif + +#ifdef ANDROID + __android_log_print(ANDROID_LOG_INFO, "Gecko", "%s", buf.buffer); +#endif + + // Write the message to stderr unless it's a warning and MOZ_IGNORE_WARNINGS + // is set. + if (!(PR_GetEnv("MOZ_IGNORE_WARNINGS") && aSeverity == NS_DEBUG_WARNING)) { + fprintf(stderr, "%s\n", buf.buffer); + fflush(stderr); + } + + switch (aSeverity) { + case NS_DEBUG_WARNING: + return; + + case NS_DEBUG_BREAK: + Break(buf.buffer); + return; + + case NS_DEBUG_ABORT: { +#if defined(MOZ_CRASHREPORTER) + // Updating crash annotations in the child causes us to do IPC. This can + // really cause trouble if we're asserting from within IPC code. So we + // have to do without the annotations in that case. + if (XRE_IsParentProcess()) { + // Don't include the PID in the crash report annotation to + // allow faceting on crash-stats.mozilla.org. + nsCString note("xpcom_runtime_abort("); + note += nonPIDBuf.buffer; + note += ")"; + CrashReporter::AppendAppNotesToCrashReport(note); + CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("AbortMessage"), + nsDependentCString(nonPIDBuf.buffer)); + } +#endif // MOZ_CRASHREPORTER + +#if defined(DEBUG) && defined(_WIN32) + RealBreak(); +#endif +#if defined(DEBUG) + nsTraceRefcnt::WalkTheStack(stderr); +#endif + Abort(buf.buffer); + return; + } + } + + // Now we deal with assertions + gAssertionCount++; + + switch (GetAssertBehavior()) { + case NS_ASSERT_WARN: + return; + + case NS_ASSERT_SUSPEND: +#ifdef XP_UNIX + fprintf(stderr, "Suspending process; attach with the debugger.\n"); + kill(0, SIGSTOP); +#else + Break(buf.buffer); +#endif + return; + + case NS_ASSERT_STACK: + nsTraceRefcnt::WalkTheStack(stderr); + return; + + case NS_ASSERT_STACK_AND_ABORT: + nsTraceRefcnt::WalkTheStack(stderr); + // Fall through to abort + MOZ_FALLTHROUGH; + + case NS_ASSERT_ABORT: + Abort(buf.buffer); + return; + + case NS_ASSERT_TRAP: + case NS_ASSERT_UNINITIALIZED: // Default to "trap" behavior + Break(buf.buffer); + return; + } +} + +static void +Abort(const char* aMsg) +{ + mozalloc_abort(aMsg); +} + +static void +RealBreak() +{ +#if defined(_WIN32) + ::DebugBreak(); +#elif defined(XP_MACOSX) + raise(SIGTRAP); +#elif defined(__GNUC__) && (defined(__i386__) || defined(__i386) || defined(__x86_64__)) + asm("int $3"); +#elif defined(__arm__) + asm( +#ifdef __ARM_ARCH_4T__ + /* ARMv4T doesn't support the BKPT instruction, so if the compiler target + * is ARMv4T, we want to ensure the assembler will understand that ARMv5T + * instruction, while keeping the resulting object tagged as ARMv4T. + */ + ".arch armv5t\n" + ".object_arch armv4t\n" +#endif + "BKPT #0"); +#elif defined(SOLARIS) +#if defined(__i386__) || defined(__i386) || defined(__x86_64__) + asm("int $3"); +#else + raise(SIGTRAP); +#endif +#else +#warning do not know how to break on this platform +#endif +} + +// Abort() calls this function, don't call it! +static void +Break(const char* aMsg) +{ +#if defined(_WIN32) + static int ignoreDebugger; + if (!ignoreDebugger) { + const char* shouldIgnoreDebugger = getenv("XPCOM_DEBUG_DLG"); + ignoreDebugger = + 1 + (shouldIgnoreDebugger && !strcmp(shouldIgnoreDebugger, "1")); + } + if ((ignoreDebugger == 2) || !::IsDebuggerPresent()) { + DWORD code = IDRETRY; + + /* Create the debug dialog out of process to avoid the crashes caused by + * Windows events leaking into our event loop from an in process dialog. + * We do this by launching windbgdlg.exe (built in xpcom/windbgdlg). + * See http://bugzilla.mozilla.org/show_bug.cgi?id=54792 + */ + PROCESS_INFORMATION pi; + STARTUPINFOW si; + wchar_t executable[MAX_PATH]; + wchar_t* pName; + + memset(&pi, 0, sizeof(pi)); + + memset(&si, 0, sizeof(si)); + si.cb = sizeof(si); + si.wShowWindow = SW_SHOW; + + // 2nd arg of CreateProcess is in/out + wchar_t* msgCopy = (wchar_t*)_alloca((strlen(aMsg) + 1) * sizeof(wchar_t)); + wcscpy(msgCopy, NS_ConvertUTF8toUTF16(aMsg).get()); + + if (GetModuleFileNameW(GetModuleHandleW(L"xpcom.dll"), executable, MAX_PATH) && + (pName = wcsrchr(executable, '\\')) != nullptr && + wcscpy(pName + 1, L"windbgdlg.exe") && + CreateProcessW(executable, msgCopy, nullptr, nullptr, + false, DETACHED_PROCESS | NORMAL_PRIORITY_CLASS, + nullptr, nullptr, &si, &pi)) { + WaitForSingleObject(pi.hProcess, INFINITE); + GetExitCodeProcess(pi.hProcess, &code); + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + } + + switch (code) { + case IDABORT: + //This should exit us + raise(SIGABRT); + //If we are ignored exit this way.. + _exit(3); + + case IDIGNORE: + return; + } + } + + RealBreak(); +#elif defined(XP_MACOSX) + /* Note that we put this Mac OS X test above the GNUC/x86 test because the + * GNUC/x86 test is also true on Intel Mac OS X and we want the PPC/x86 + * impls to be the same. + */ + RealBreak(); +#elif defined(__GNUC__) && (defined(__i386__) || defined(__i386) || defined(__x86_64__)) + RealBreak(); +#elif defined(__arm__) + RealBreak(); +#elif defined(SOLARIS) + RealBreak(); +#else +#warning do not know how to break on this platform +#endif +} + +nsresult +nsDebugImpl::Create(nsISupports* aOuter, const nsIID& aIID, void** aInstancePtr) +{ + static const nsDebugImpl* sImpl; + + if (NS_WARN_IF(aOuter)) { + return NS_ERROR_NO_AGGREGATION; + } + + if (!sImpl) { + sImpl = new nsDebugImpl(); + } + + return const_cast<nsDebugImpl*>(sImpl)->QueryInterface(aIID, aInstancePtr); +} + +//////////////////////////////////////////////////////////////////////////////// + +nsresult +NS_ErrorAccordingToNSPR() +{ + PRErrorCode err = PR_GetError(); + switch (err) { + case PR_OUT_OF_MEMORY_ERROR: return NS_ERROR_OUT_OF_MEMORY; + case PR_WOULD_BLOCK_ERROR: return NS_BASE_STREAM_WOULD_BLOCK; + case PR_FILE_NOT_FOUND_ERROR: return NS_ERROR_FILE_NOT_FOUND; + case PR_READ_ONLY_FILESYSTEM_ERROR: return NS_ERROR_FILE_READ_ONLY; + case PR_NOT_DIRECTORY_ERROR: return NS_ERROR_FILE_NOT_DIRECTORY; + case PR_IS_DIRECTORY_ERROR: return NS_ERROR_FILE_IS_DIRECTORY; + case PR_LOOP_ERROR: return NS_ERROR_FILE_UNRESOLVABLE_SYMLINK; + case PR_FILE_EXISTS_ERROR: return NS_ERROR_FILE_ALREADY_EXISTS; + case PR_FILE_IS_LOCKED_ERROR: return NS_ERROR_FILE_IS_LOCKED; + case PR_FILE_TOO_BIG_ERROR: return NS_ERROR_FILE_TOO_BIG; + case PR_NO_DEVICE_SPACE_ERROR: return NS_ERROR_FILE_NO_DEVICE_SPACE; + case PR_NAME_TOO_LONG_ERROR: return NS_ERROR_FILE_NAME_TOO_LONG; + case PR_DIRECTORY_NOT_EMPTY_ERROR: return NS_ERROR_FILE_DIR_NOT_EMPTY; + case PR_NO_ACCESS_RIGHTS_ERROR: return NS_ERROR_FILE_ACCESS_DENIED; + default: return NS_ERROR_FAILURE; + } +} + +void +NS_ABORT_OOM(size_t aSize) +{ +#if defined(MOZ_CRASHREPORTER) + CrashReporter::AnnotateOOMAllocationSize(aSize); +#endif + MOZ_CRASH("OOM"); +} diff --git a/xpcom/base/nsDebugImpl.h b/xpcom/base/nsDebugImpl.h new file mode 100644 index 000000000..23680238a --- /dev/null +++ b/xpcom/base/nsDebugImpl.h @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsDebugImpl_h +#define nsDebugImpl_h + +#include "nsIDebug2.h" + +class nsDebugImpl : public nsIDebug2 +{ +public: + nsDebugImpl() = default; + NS_DECL_ISUPPORTS + NS_DECL_NSIDEBUG2 + + static nsresult Create(nsISupports* aOuter, const nsIID& aIID, + void** aInstancePtr); + + /* + * Inform nsDebugImpl that we're in multiprocess mode. + * + * If aDesc is not nullptr, the string it points to must be + * statically-allocated (i.e., it must be a string literal). + */ + static void SetMultiprocessMode(const char* aDesc); +}; + + +#define NS_DEBUG_CONTRACTID "@mozilla.org/xpcom/debug;1" +#define NS_DEBUG_CID \ +{ /* cb6cdb94-e417-4601-b4a5-f991bf41453d */ \ + 0xcb6cdb94, \ + 0xe417, \ + 0x4601, \ + {0xb4, 0xa5, 0xf9, 0x91, 0xbf, 0x41, 0x45, 0x3d} \ +} + +#endif // nsDebugImpl_h diff --git a/xpcom/base/nsDumpUtils.cpp b/xpcom/base/nsDumpUtils.cpp new file mode 100644 index 000000000..c68862d08 --- /dev/null +++ b/xpcom/base/nsDumpUtils.cpp @@ -0,0 +1,513 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsDumpUtils.h" +#include "nsDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" +#include "prenv.h" +#include <errno.h> +#include "mozilla/Services.h" +#include "nsIObserverService.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/Unused.h" + +#ifdef XP_UNIX // { +#include "mozilla/Preferences.h" +#include <fcntl.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> + +using namespace mozilla; + +/* + * The following code supports triggering a registered callback upon + * receiving a specific signal. + * + * Take about:memory for example, we register + * 1. doGCCCDump for doMemoryReport + * 2. doMemoryReport for sDumpAboutMemorySignum(SIGRTMIN) + * and sDumpAboutMemoryAfterMMUSignum(SIGRTMIN+1). + * + * When we receive one of these signals, we write the signal number to a pipe. + * The IO thread then notices that the pipe has been written to, and kicks off + * the appropriate task on the main thread. + * + * This scheme is similar to using signalfd(), except it's portable and it + * doesn't require the use of sigprocmask, which is problematic because it + * masks signals received by child processes. + * + * In theory, we could use Chromium's MessageLoopForIO::CatchSignal() for this. + * But that uses libevent, which does not handle the realtime signals (bug + * 794074). + */ + +// This is the write-end of a pipe that we use to notice when a +// specific signal occurs. +static Atomic<int> sDumpPipeWriteFd(-1); + +const char* const FifoWatcher::kPrefName = + "memory_info_dumper.watch_fifo.enabled"; + +static void +DumpSignalHandler(int aSignum) +{ + // This is a signal handler, so everything in here needs to be + // async-signal-safe. Be careful! + + if (sDumpPipeWriteFd != -1) { + uint8_t signum = static_cast<int>(aSignum); + Unused << write(sDumpPipeWriteFd, &signum, sizeof(signum)); + } +} + +NS_IMPL_ISUPPORTS(FdWatcher, nsIObserver); + +void +FdWatcher::Init() +{ + MOZ_ASSERT(NS_IsMainThread()); + + nsCOMPtr<nsIObserverService> os = services::GetObserverService(); + os->AddObserver(this, "xpcom-shutdown", /* ownsWeak = */ false); + + XRE_GetIOMessageLoop()->PostTask(NewRunnableMethod(this, &FdWatcher::StartWatching)); +} + +// Implementations may call this function multiple times if they ensure that +// it's safe to call OpenFd() multiple times and they call StopWatching() +// first. +void +FdWatcher::StartWatching() +{ + MOZ_ASSERT(XRE_GetIOMessageLoop() == MessageLoopForIO::current()); + MOZ_ASSERT(mFd == -1); + + mFd = OpenFd(); + if (mFd == -1) { + LOG("FdWatcher: OpenFd failed."); + return; + } + + MessageLoopForIO::current()->WatchFileDescriptor( + mFd, /* persistent = */ true, + MessageLoopForIO::WATCH_READ, + &mReadWatcher, this); +} + +// Since implementations can call StartWatching() multiple times, they can of +// course call StopWatching() multiple times. +void +FdWatcher::StopWatching() +{ + MOZ_ASSERT(XRE_GetIOMessageLoop() == MessageLoopForIO::current()); + + mReadWatcher.StopWatchingFileDescriptor(); + if (mFd != -1) { + close(mFd); + mFd = -1; + } +} + +StaticRefPtr<SignalPipeWatcher> SignalPipeWatcher::sSingleton; + +/* static */ SignalPipeWatcher* +SignalPipeWatcher::GetSingleton() +{ + if (!sSingleton) { + sSingleton = new SignalPipeWatcher(); + sSingleton->Init(); + ClearOnShutdown(&sSingleton); + } + return sSingleton; +} + +void +SignalPipeWatcher::RegisterCallback(uint8_t aSignal, + PipeCallback aCallback) +{ + MutexAutoLock lock(mSignalInfoLock); + + for (SignalInfoArray::index_type i = 0; i < mSignalInfo.Length(); ++i) { + if (mSignalInfo[i].mSignal == aSignal) { + LOG("Register Signal(%d) callback failed! (DUPLICATE)", aSignal); + return; + } + } + SignalInfo signalInfo = { aSignal, aCallback }; + mSignalInfo.AppendElement(signalInfo); + RegisterSignalHandler(signalInfo.mSignal); +} + +void +SignalPipeWatcher::RegisterSignalHandler(uint8_t aSignal) +{ + struct sigaction action; + memset(&action, 0, sizeof(action)); + sigemptyset(&action.sa_mask); + action.sa_handler = DumpSignalHandler; + + if (aSignal) { + if (sigaction(aSignal, &action, nullptr)) { + LOG("SignalPipeWatcher failed to register sig %d.", aSignal); + } + } else { + MutexAutoLock lock(mSignalInfoLock); + for (SignalInfoArray::index_type i = 0; i < mSignalInfo.Length(); i++) { + if (sigaction(mSignalInfo[i].mSignal, &action, nullptr)) { + LOG("SignalPipeWatcher failed to register signal(%d) " + "dump signal handler.", mSignalInfo[i].mSignal); + } + } + } +} + +SignalPipeWatcher::~SignalPipeWatcher() +{ + if (sDumpPipeWriteFd != -1) { + StopWatching(); + } +} + +int +SignalPipeWatcher::OpenFd() +{ + MOZ_ASSERT(XRE_GetIOMessageLoop() == MessageLoopForIO::current()); + + // Create a pipe. When we receive a signal in our signal handler, we'll + // write the signum to the write-end of this pipe. + int pipeFds[2]; + if (pipe(pipeFds)) { + LOG("SignalPipeWatcher failed to create pipe."); + return -1; + } + + // Close this pipe on calls to exec(). + fcntl(pipeFds[0], F_SETFD, FD_CLOEXEC); + fcntl(pipeFds[1], F_SETFD, FD_CLOEXEC); + + int readFd = pipeFds[0]; + sDumpPipeWriteFd = pipeFds[1]; + + RegisterSignalHandler(); + return readFd; +} + +void +SignalPipeWatcher::StopWatching() +{ + MOZ_ASSERT(XRE_GetIOMessageLoop() == MessageLoopForIO::current()); + + // Close sDumpPipeWriteFd /after/ setting the fd to -1. + // Otherwise we have the (admittedly far-fetched) race where we + // + // 1) close sDumpPipeWriteFd + // 2) open a new fd with the same number as sDumpPipeWriteFd + // had. + // 3) receive a signal, then write to the fd. + int pipeWriteFd = sDumpPipeWriteFd.exchange(-1); + close(pipeWriteFd); + + FdWatcher::StopWatching(); +} + +void +SignalPipeWatcher::OnFileCanReadWithoutBlocking(int aFd) +{ + MOZ_ASSERT(XRE_GetIOMessageLoop() == MessageLoopForIO::current()); + + uint8_t signum; + ssize_t numReceived = read(aFd, &signum, sizeof(signum)); + if (numReceived != sizeof(signum)) { + LOG("Error reading from buffer in " + "SignalPipeWatcher::OnFileCanReadWithoutBlocking."); + return; + } + + { + MutexAutoLock lock(mSignalInfoLock); + for (SignalInfoArray::index_type i = 0; i < mSignalInfo.Length(); i++) { + if (signum == mSignalInfo[i].mSignal) { + mSignalInfo[i].mCallback(signum); + return; + } + } + } + LOG("SignalPipeWatcher got unexpected signum."); +} + +StaticRefPtr<FifoWatcher> FifoWatcher::sSingleton; + +/* static */ FifoWatcher* +FifoWatcher::GetSingleton() +{ + if (!sSingleton) { + nsAutoCString dirPath; + Preferences::GetCString( + "memory_info_dumper.watch_fifo.directory", &dirPath); + sSingleton = new FifoWatcher(dirPath); + sSingleton->Init(); + ClearOnShutdown(&sSingleton); + } + return sSingleton; +} + +/* static */ bool +FifoWatcher::MaybeCreate() +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (!XRE_IsParentProcess()) { + // We want this to be main-process only, since two processes can't listen + // to the same fifo. + return false; + } + + if (!Preferences::GetBool(kPrefName, false)) { + LOG("Fifo watcher disabled via pref."); + return false; + } + + // The FifoWatcher is held alive by the observer service. + if (!sSingleton) { + GetSingleton(); + } + return true; +} + +void +FifoWatcher::RegisterCallback(const nsCString& aCommand, FifoCallback aCallback) +{ + MutexAutoLock lock(mFifoInfoLock); + + for (FifoInfoArray::index_type i = 0; i < mFifoInfo.Length(); ++i) { + if (mFifoInfo[i].mCommand.Equals(aCommand)) { + LOG("Register command(%s) callback failed! (DUPLICATE)", aCommand.get()); + return; + } + } + FifoInfo aFifoInfo = { aCommand, aCallback }; + mFifoInfo.AppendElement(aFifoInfo); +} + +FifoWatcher::~FifoWatcher() +{ +} + +int +FifoWatcher::OpenFd() +{ + // If the memory_info_dumper.directory pref is specified, put the fifo + // there. Otherwise, put it into the system's tmp directory. + + nsCOMPtr<nsIFile> file; + + nsresult rv; + if (mDirPath.Length() > 0) { + rv = XRE_GetFileFromPath(mDirPath.get(), getter_AddRefs(file)); + if (NS_FAILED(rv)) { + LOG("FifoWatcher failed to open file \"%s\"", mDirPath.get()); + return -1; + } + } else { + rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(file)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return -1; + } + } + + rv = file->AppendNative(NS_LITERAL_CSTRING("debug_info_trigger")); + if (NS_WARN_IF(NS_FAILED(rv))) { + return -1; + } + + nsAutoCString path; + rv = file->GetNativePath(path); + if (NS_WARN_IF(NS_FAILED(rv))) { + return -1; + } + + // unlink might fail because the file doesn't exist, or for other reasons. + // But we don't care it fails; any problems will be detected later, when we + // try to mkfifo or open the file. + if (unlink(path.get())) { + LOG("FifoWatcher::OpenFifo unlink failed; errno=%d. " + "Continuing despite error.", errno); + } + + if (mkfifo(path.get(), 0766)) { + LOG("FifoWatcher::OpenFifo mkfifo failed; errno=%d", errno); + return -1; + } + +#ifdef ANDROID + // Android runs with a umask, so we need to chmod our fifo to make it + // world-writable. + chmod(path.get(), 0666); +#endif + + int fd; + do { + // The fifo will block until someone else has written to it. In + // particular, open() will block until someone else has opened it for + // writing! We want open() to succeed and read() to block, so we open + // with NONBLOCK and then fcntl that away. + fd = open(path.get(), O_RDONLY | O_NONBLOCK); + } while (fd == -1 && errno == EINTR); + + if (fd == -1) { + LOG("FifoWatcher::OpenFifo open failed; errno=%d", errno); + return -1; + } + + // Make fd blocking now that we've opened it. + if (fcntl(fd, F_SETFL, 0)) { + close(fd); + return -1; + } + + return fd; +} + +void +FifoWatcher::OnFileCanReadWithoutBlocking(int aFd) +{ + MOZ_ASSERT(XRE_GetIOMessageLoop() == MessageLoopForIO::current()); + + char buf[1024]; + int nread; + do { + // sizeof(buf) - 1 to leave space for the null-terminator. + nread = read(aFd, buf, sizeof(buf)); + } while (nread == -1 && errno == EINTR); + + if (nread == -1) { + // We want to avoid getting into a situation where + // OnFileCanReadWithoutBlocking is called in an infinite loop, so when + // something goes wrong, stop watching the fifo altogether. + LOG("FifoWatcher hit an error (%d) and is quitting.", errno); + StopWatching(); + return; + } + + if (nread == 0) { + // If we get EOF, that means that the other side closed the fifo. We need + // to close and re-open the fifo; if we don't, + // OnFileCanWriteWithoutBlocking will be called in an infinite loop. + + LOG("FifoWatcher closing and re-opening fifo."); + StopWatching(); + StartWatching(); + return; + } + + nsAutoCString inputStr; + inputStr.Append(buf, nread); + + // Trimming whitespace is important because if you do + // |echo "foo" >> debug_info_trigger|, + // it'll actually write "foo\n" to the fifo. + inputStr.Trim("\b\t\r\n"); + + { + MutexAutoLock lock(mFifoInfoLock); + + for (FifoInfoArray::index_type i = 0; i < mFifoInfo.Length(); i++) { + const nsCString commandStr = mFifoInfo[i].mCommand; + if (inputStr == commandStr.get()) { + mFifoInfo[i].mCallback(inputStr); + return; + } + } + } + LOG("Got unexpected value from fifo; ignoring it."); +} + +#endif // XP_UNIX } + +// In Android case, this function will open a file named aFilename under +// /data/local/tmp/"aFoldername". +// Otherwise, it will open a file named aFilename under "NS_OS_TEMP_DIR". +/* static */ nsresult +nsDumpUtils::OpenTempFile(const nsACString& aFilename, nsIFile** aFile, + const nsACString& aFoldername, Mode aMode) +{ +#ifdef ANDROID + // For Android, first try the downloads directory which is world-readable + // rather than the temp directory which is not. + if (!*aFile) { + char* env = PR_GetEnv("DOWNLOADS_DIRECTORY"); + if (env) { + NS_NewNativeLocalFile(nsCString(env), /* followLinks = */ true, aFile); + } + } +#endif + nsresult rv; + if (!*aFile) { + rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, aFile); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + +#ifdef ANDROID + // /data/local/tmp is a true tmp directory; anyone can create a file there, + // but only the user which created the file can remove it. We want non-root + // users to be able to remove these files, so we write them into a + // subdirectory of the temp directory and chmod 777 that directory. + if (aFoldername != EmptyCString()) { + rv = (*aFile)->AppendNative(aFoldername); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // It's OK if this fails; that probably just means that the directory + // already exists. + Unused << (*aFile)->Create(nsIFile::DIRECTORY_TYPE, 0777); + + nsAutoCString dirPath; + rv = (*aFile)->GetNativePath(dirPath); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + while (chmod(dirPath.get(), 0777) == -1 && errno == EINTR) { + } + } +#endif + + nsCOMPtr<nsIFile> file(*aFile); + + rv = file->AppendNative(aFilename); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (aMode == CREATE_UNIQUE) { + rv = file->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0666); + } else { + rv = file->Create(nsIFile::NORMAL_FILE_TYPE, 0666); + } + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + +#ifdef ANDROID + // Make this file world-read/writable; the permissions passed to the + // CreateUnique call above are not sufficient on Android, which runs with a + // umask. + nsAutoCString path; + rv = file->GetNativePath(path); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + while (chmod(path.get(), 0666) == -1 && errno == EINTR) { + } +#endif + + return NS_OK; +} diff --git a/xpcom/base/nsDumpUtils.h b/xpcom/base/nsDumpUtils.h new file mode 100644 index 000000000..12a99da18 --- /dev/null +++ b/xpcom/base/nsDumpUtils.h @@ -0,0 +1,203 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_nsDumpUtils_h +#define mozilla_nsDumpUtils_h + +#include "nsIObserver.h" +#include "base/message_loop.h" +#include "nsXULAppAPI.h" +#include "nsThreadUtils.h" +#include "mozilla/Mutex.h" +#include "mozilla/StaticPtr.h" +#include "nsTArray.h" + +#ifdef LOG +#undef LOG +#endif + +#ifdef ANDROID +#include "android/log.h" +#define LOG(...) __android_log_print(ANDROID_LOG_INFO, "Gecko:DumpUtils", ## __VA_ARGS__) +#else +#define LOG(...) +#endif + +#ifdef XP_UNIX // { + +/** + * Abstract base class for something which watches an fd and takes action when + * we can read from it without blocking. + */ +class FdWatcher + : public MessageLoopForIO::Watcher + , public nsIObserver +{ +protected: + MessageLoopForIO::FileDescriptorWatcher mReadWatcher; + int mFd; + + virtual ~FdWatcher() + { + // StopWatching should have run. + MOZ_ASSERT(mFd == -1); + } + +public: + FdWatcher() + : mFd(-1) + { + MOZ_ASSERT(NS_IsMainThread()); + } + + /** + * Open the fd to watch. If we encounter an error, return -1. + */ + virtual int OpenFd() = 0; + + /** + * Called when you can read() from the fd without blocking. Note that this + * function is also called when you're at eof (read() returns 0 in this case). + */ + virtual void OnFileCanReadWithoutBlocking(int aFd) override = 0; + virtual void OnFileCanWriteWithoutBlocking(int aFd) override {}; + + NS_DECL_THREADSAFE_ISUPPORTS + + /** + * Initialize this object. This should be called right after the object is + * constructed. (This would go in the constructor, except we interact with + * XPCOM, which we can't do from a constructor because our refcount is 0 at + * that point.) + */ + void Init(); + + // Implementations may call this function multiple times if they ensure that + + virtual void StartWatching(); + + // Since implementations can call StartWatching() multiple times, they can of + // course call StopWatching() multiple times. + virtual void StopWatching(); + + NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) override + { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!strcmp(aTopic, "xpcom-shutdown")); + + XRE_GetIOMessageLoop()->PostTask(mozilla::NewRunnableMethod(this, &FdWatcher::StopWatching)); + + return NS_OK; + } +}; + +typedef void (*FifoCallback)(const nsCString& aInputStr); +struct FifoInfo +{ + nsCString mCommand; + FifoCallback mCallback; +}; +typedef nsTArray<FifoInfo> FifoInfoArray; + +class FifoWatcher : public FdWatcher +{ +public: + /** + * The name of the preference used to enable/disable the FifoWatcher. + */ + static const char* const kPrefName; + + static FifoWatcher* GetSingleton(); + + static bool MaybeCreate(); + + void RegisterCallback(const nsCString& aCommand, FifoCallback aCallback); + + virtual ~FifoWatcher(); + + virtual int OpenFd(); + + virtual void OnFileCanReadWithoutBlocking(int aFd); + +private: + nsAutoCString mDirPath; + + static mozilla::StaticRefPtr<FifoWatcher> sSingleton; + + explicit FifoWatcher(nsCString aPath) + : mDirPath(aPath) + , mFifoInfoLock("FifoWatcher.mFifoInfoLock") + { + } + + mozilla::Mutex mFifoInfoLock; // protects mFifoInfo + FifoInfoArray mFifoInfo; +}; + +typedef void (*PipeCallback)(const uint8_t aRecvSig); +struct SignalInfo +{ + uint8_t mSignal; + PipeCallback mCallback; +}; +typedef nsTArray<SignalInfo> SignalInfoArray; + +class SignalPipeWatcher : public FdWatcher +{ +public: + static SignalPipeWatcher* GetSingleton(); + + void RegisterCallback(uint8_t aSignal, PipeCallback aCallback); + + void RegisterSignalHandler(uint8_t aSignal = 0); + + virtual ~SignalPipeWatcher(); + + virtual int OpenFd(); + + virtual void StopWatching(); + + virtual void OnFileCanReadWithoutBlocking(int aFd); + +private: + static mozilla::StaticRefPtr<SignalPipeWatcher> sSingleton; + + SignalPipeWatcher() + : mSignalInfoLock("SignalPipeWatcher.mSignalInfoLock") + { + MOZ_ASSERT(NS_IsMainThread()); + } + + mozilla::Mutex mSignalInfoLock; // protects mSignalInfo + SignalInfoArray mSignalInfo; +}; + +#endif // XP_UNIX } + +class nsDumpUtils +{ +public: + + enum Mode { + CREATE, + CREATE_UNIQUE + }; + + /** + * This function creates a new unique file based on |aFilename| in a + * world-readable temp directory. This is the system temp directory + * or, in the case of Android, the downloads directory. If |aFile| is + * non-null, it is assumed to point to a folder, and that folder is used + * instead. + */ + static nsresult OpenTempFile(const nsACString& aFilename, + nsIFile** aFile, + const nsACString& aFoldername = EmptyCString(), + Mode aMode = CREATE_UNIQUE); +}; + +#endif diff --git a/xpcom/base/nsError.h b/xpcom/base/nsError.h new file mode 100644 index 000000000..b9e5d23f6 --- /dev/null +++ b/xpcom/base/nsError.h @@ -0,0 +1,219 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsError_h__ +#define nsError_h__ + +#ifndef __cplusplus +#error nsError.h no longer supports C sources +#endif + +#include "mozilla/Attributes.h" +#include "mozilla/Likely.h" + +#include <stdint.h> + +/* + * To add error code to your module, you need to do the following: + * + * 1) Add a module offset code. Add yours to the bottom of the list + * right below this comment, adding 1. + * + * 2) In your module, define a header file which uses one of the + * NE_ERROR_GENERATExxxxxx macros. Some examples below: + * + * #define NS_ERROR_MYMODULE_MYERROR1 NS_ERROR_GENERATE(NS_ERROR_SEVERITY_ERROR,NS_ERROR_MODULE_MYMODULE,1) + * #define NS_ERROR_MYMODULE_MYERROR2 NS_ERROR_GENERATE_SUCCESS(NS_ERROR_MODULE_MYMODULE,2) + * #define NS_ERROR_MYMODULE_MYERROR3 NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_MYMODULE,3) + * + */ + + +/** + * @name Standard Module Offset Code. Each Module should identify a unique number + * and then all errors associated with that module become offsets from the + * base associated with that module id. There are 16 bits of code bits for + * each module. + */ + +#define NS_ERROR_MODULE_XPCOM 1 +#define NS_ERROR_MODULE_BASE 2 +#define NS_ERROR_MODULE_GFX 3 +#define NS_ERROR_MODULE_WIDGET 4 +#define NS_ERROR_MODULE_CALENDAR 5 +#define NS_ERROR_MODULE_NETWORK 6 +#define NS_ERROR_MODULE_PLUGINS 7 +#define NS_ERROR_MODULE_LAYOUT 8 +#define NS_ERROR_MODULE_HTMLPARSER 9 +#define NS_ERROR_MODULE_RDF 10 +#define NS_ERROR_MODULE_UCONV 11 +#define NS_ERROR_MODULE_REG 12 +#define NS_ERROR_MODULE_FILES 13 +#define NS_ERROR_MODULE_DOM 14 +#define NS_ERROR_MODULE_IMGLIB 15 +#define NS_ERROR_MODULE_MAILNEWS 16 +#define NS_ERROR_MODULE_EDITOR 17 +#define NS_ERROR_MODULE_XPCONNECT 18 +#define NS_ERROR_MODULE_PROFILE 19 +#define NS_ERROR_MODULE_LDAP 20 +#define NS_ERROR_MODULE_SECURITY 21 +#define NS_ERROR_MODULE_DOM_XPATH 22 +/* 23 used to be NS_ERROR_MODULE_DOM_RANGE (see bug 711047) */ +#define NS_ERROR_MODULE_URILOADER 24 +#define NS_ERROR_MODULE_CONTENT 25 +#define NS_ERROR_MODULE_PYXPCOM 26 +#define NS_ERROR_MODULE_XSLT 27 +#define NS_ERROR_MODULE_IPC 28 +#define NS_ERROR_MODULE_SVG 29 +#define NS_ERROR_MODULE_STORAGE 30 +#define NS_ERROR_MODULE_SCHEMA 31 +#define NS_ERROR_MODULE_DOM_FILE 32 +#define NS_ERROR_MODULE_DOM_INDEXEDDB 33 +#define NS_ERROR_MODULE_DOM_FILEHANDLE 34 +#define NS_ERROR_MODULE_SIGNED_JAR 35 +#define NS_ERROR_MODULE_DOM_FILESYSTEM 36 +#define NS_ERROR_MODULE_DOM_BLUETOOTH 37 +#define NS_ERROR_MODULE_SIGNED_APP 38 +#define NS_ERROR_MODULE_DOM_ANIM 39 +#define NS_ERROR_MODULE_DOM_PUSH 40 +#define NS_ERROR_MODULE_DOM_MEDIA 41 + +/* NS_ERROR_MODULE_GENERAL should be used by modules that do not + * care if return code values overlap. Callers of methods that + * return such codes should be aware that they are not + * globally unique. Implementors should be careful about blindly + * returning codes from other modules that might also use + * the generic base. + */ +#define NS_ERROR_MODULE_GENERAL 51 + +/** + * @name Severity Code. This flag identifies the level of warning + */ + +#define NS_ERROR_SEVERITY_SUCCESS 0 +#define NS_ERROR_SEVERITY_ERROR 1 + +/** + * @name Mozilla Code. This flag separates consumers of mozilla code + * from the native platform + */ + +#define NS_ERROR_MODULE_BASE_OFFSET 0x45 + +/* Helpers for defining our enum, to be undef'd later */ +#define SUCCESS_OR_FAILURE(sev, module, code) \ + ((uint32_t)(sev) << 31) | \ + ((uint32_t)(module + NS_ERROR_MODULE_BASE_OFFSET) << 16) | \ + (uint32_t)(code) +#define SUCCESS(code) \ + SUCCESS_OR_FAILURE(NS_ERROR_SEVERITY_SUCCESS, MODULE, code) +#define FAILURE(code) \ + SUCCESS_OR_FAILURE(NS_ERROR_SEVERITY_ERROR, MODULE, code) + +/** + * @name Standard return values + */ + +/*@{*/ + +enum class nsresult : uint32_t +{ + #undef ERROR + #define ERROR(key, val) key = val + #include "ErrorList.h" + #undef ERROR +}; + +/* + * enum classes don't place their initializers in the global scope, so we need + * constants for compatibility with old code. + */ +const nsresult + #define ERROR(key, val) key = nsresult::key + #include "ErrorList.h" + #undef ERROR +; + +#undef SUCCESS_OR_FAILURE +#undef SUCCESS +#undef FAILURE + +/** + * @name Standard Error Handling Macros + * @return 0 or 1 (false/true with bool type for C++) + */ + +inline uint32_t +NS_FAILED_impl(nsresult aErr) +{ + return static_cast<uint32_t>(aErr) & 0x80000000; +} +#define NS_FAILED(_nsresult) ((bool)MOZ_UNLIKELY(NS_FAILED_impl(_nsresult))) +#define NS_SUCCEEDED(_nsresult) ((bool)MOZ_LIKELY(!NS_FAILED_impl(_nsresult))) + +/* Check that our enum type is actually uint32_t as expected */ +static_assert(((nsresult)0) < ((nsresult)-1), + "nsresult must be an unsigned type"); +static_assert(sizeof(nsresult) == sizeof(uint32_t), + "nsresult must be 32 bits"); + +#define MOZ_ALWAYS_SUCCEEDS(expr) MOZ_ALWAYS_TRUE(NS_SUCCEEDED(expr)) + +/** + * @name Standard Error Generating Macros + */ + +#define NS_ERROR_GENERATE(sev, module, code) \ + (nsresult)(((uint32_t)(sev) << 31) | \ + ((uint32_t)(module + NS_ERROR_MODULE_BASE_OFFSET) << 16) | \ + ((uint32_t)(code))) + +#define NS_ERROR_GENERATE_SUCCESS(module, code) \ + NS_ERROR_GENERATE(NS_ERROR_SEVERITY_SUCCESS, module, code) + +#define NS_ERROR_GENERATE_FAILURE(module, code) \ + NS_ERROR_GENERATE(NS_ERROR_SEVERITY_ERROR, module, code) + + /* + * This will return the nsresult corresponding to the most recent NSPR failure + * returned by PR_GetError. + * + *********************************************************************** + * Do not depend on this function. It will be going away! + *********************************************************************** + */ +extern nsresult +NS_ErrorAccordingToNSPR(); + + +/** + * @name Standard Macros for retrieving error bits + */ + +inline constexpr uint16_t +NS_ERROR_GET_CODE(nsresult aErr) +{ + return uint32_t(aErr) & 0xffff; +} +inline constexpr uint16_t +NS_ERROR_GET_MODULE(nsresult aErr) +{ + return ((uint32_t(aErr) >> 16) - NS_ERROR_MODULE_BASE_OFFSET) & 0x1fff; +} +inline bool +NS_ERROR_GET_SEVERITY(nsresult aErr) +{ + return uint32_t(aErr) >> 31; +} + + +#ifdef _MSC_VER +#pragma warning(disable: 4251) /* 'nsCOMPtr<class nsIInputStream>' needs to have dll-interface to be used by clients of class 'nsInputStream' */ +#pragma warning(disable: 4275) /* non dll-interface class 'nsISupports' used as base for dll-interface class 'nsIRDFNode' */ +#endif + +#endif diff --git a/xpcom/base/nsErrorService.cpp b/xpcom/base/nsErrorService.cpp new file mode 100644 index 000000000..d39b4f31e --- /dev/null +++ b/xpcom/base/nsErrorService.cpp @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsErrorService.h" +#include "nsCRTGlue.h" +#include "nsAutoPtr.h" + +NS_IMPL_ISUPPORTS(nsErrorService, nsIErrorService) + +nsresult +nsErrorService::Create(nsISupports* aOuter, const nsIID& aIID, + void** aInstancePtr) +{ + if (NS_WARN_IF(aOuter)) { + return NS_ERROR_NO_AGGREGATION; + } + RefPtr<nsErrorService> serv = new nsErrorService(); + return serv->QueryInterface(aIID, aInstancePtr); +} + +NS_IMETHODIMP +nsErrorService::RegisterErrorStringBundle(int16_t aErrorModule, + const char* aStringBundleURL) +{ + mErrorStringBundleURLMap.Put(aErrorModule, new nsCString(aStringBundleURL)); + return NS_OK; +} + +NS_IMETHODIMP +nsErrorService::UnregisterErrorStringBundle(int16_t aErrorModule) +{ + mErrorStringBundleURLMap.Remove(aErrorModule); + return NS_OK; +} + +NS_IMETHODIMP +nsErrorService::GetErrorStringBundle(int16_t aErrorModule, char** aResult) +{ + nsCString* bundleURL = mErrorStringBundleURLMap.Get(aErrorModule); + if (!bundleURL) { + return NS_ERROR_FAILURE; + } + *aResult = ToNewCString(*bundleURL); + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/xpcom/base/nsErrorService.h b/xpcom/base/nsErrorService.h new file mode 100644 index 000000000..783c99ef9 --- /dev/null +++ b/xpcom/base/nsErrorService.h @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsErrorService_h__ +#define nsErrorService_h__ + +#include "mozilla/Attributes.h" + +#include "nsIErrorService.h" +#include "nsClassHashtable.h" +#include "nsHashKeys.h" + +class nsErrorService final : public nsIErrorService +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIERRORSERVICE + + nsErrorService() + { + } + + static nsresult + Create(nsISupports* aOuter, const nsIID& aIID, void** aInstancePtr); + +private: + ~nsErrorService() + { + } + + nsClassHashtable<nsUint32HashKey, nsCString> mErrorStringBundleURLMap; +}; + +#endif // nsErrorService_h__ diff --git a/xpcom/base/nsGZFileWriter.cpp b/xpcom/base/nsGZFileWriter.cpp new file mode 100644 index 000000000..a5bc5be39 --- /dev/null +++ b/xpcom/base/nsGZFileWriter.cpp @@ -0,0 +1,111 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsGZFileWriter.h" +#include "nsIFile.h" +#include "nsString.h" +#include "zlib.h" + +#ifdef XP_WIN +#include <io.h> +#define _dup dup +#else +#include <unistd.h> +#endif + +NS_IMPL_ISUPPORTS(nsGZFileWriter, nsIGZFileWriter) + +nsGZFileWriter::nsGZFileWriter(Operation aMode) + : mMode(aMode) + , mInitialized(false) + , mFinished(false) +{ +} + +nsGZFileWriter::~nsGZFileWriter() +{ + if (mInitialized && !mFinished) { + Finish(); + } +} + +NS_IMETHODIMP +nsGZFileWriter::Init(nsIFile* aFile) +{ + if (NS_WARN_IF(mInitialized) || + NS_WARN_IF(mFinished)) { + return NS_ERROR_FAILURE; + } + + // Get a FILE out of our nsIFile. Convert that into a file descriptor which + // gzip can own. Then close our FILE, leaving only gzip's fd open. + + FILE* file; + nsresult rv = aFile->OpenANSIFileDesc(mMode == Create ? "wb" : "ab", &file); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + return InitANSIFileDesc(file); +} + +NS_IMETHODIMP +nsGZFileWriter::InitANSIFileDesc(FILE* aFile) +{ + mGZFile = gzdopen(dup(fileno(aFile)), mMode == Create ? "wb" : "ab"); + fclose(aFile); + + // gzdopen returns nullptr on error. + if (NS_WARN_IF(!mGZFile)) { + return NS_ERROR_FAILURE; + } + + mInitialized = true; + + return NS_OK; +} + +NS_IMETHODIMP +nsGZFileWriter::Write(const nsACString& aStr) +{ + if (NS_WARN_IF(!mInitialized) || + NS_WARN_IF(mFinished)) { + return NS_ERROR_FAILURE; + } + + // gzwrite uses a return value of 0 to indicate failure. Otherwise, it + // returns the number of uncompressed bytes written. To ensure we can + // distinguish between success and failure, don't call gzwrite when we have 0 + // bytes to write. + if (aStr.IsEmpty()) { + return NS_OK; + } + + // gzwrite never does a short write -- that is, the return value should + // always be either 0 or aStr.Length(), and we shouldn't have to call it + // multiple times in order to get it to read the whole buffer. + int rv = gzwrite(mGZFile, aStr.BeginReading(), aStr.Length()); + if (NS_WARN_IF(rv != static_cast<int>(aStr.Length()))) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsGZFileWriter::Finish() +{ + if (NS_WARN_IF(!mInitialized) || + NS_WARN_IF(mFinished)) { + return NS_ERROR_FAILURE; + } + + mFinished = true; + gzclose(mGZFile); + + // Ignore errors from gzclose; it's not like there's anything we can do about + // it, at this point! + return NS_OK; +} diff --git a/xpcom/base/nsGZFileWriter.h b/xpcom/base/nsGZFileWriter.h new file mode 100644 index 000000000..4bb91173c --- /dev/null +++ b/xpcom/base/nsGZFileWriter.h @@ -0,0 +1,55 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsGZFileWriter_h +#define nsGZFileWriter_h + +#include "nsIGZFileWriter.h" +#include "zlib.h" + +/** + * A simple class for writing .gz files. + */ +class nsGZFileWriter final : public nsIGZFileWriter +{ + virtual ~nsGZFileWriter(); + +public: + + enum Operation { + Append, + Create + }; + + + explicit nsGZFileWriter(Operation aMode = Create); + + NS_DECL_ISUPPORTS + NS_DECL_NSIGZFILEWRITER + + /** + * nsIGZFileWriter exposes two non-virtual overloads of Write(). We + * duplicate them here so that you can call these overloads on a pointer to + * the concrete nsGZFileWriter class. + */ + MOZ_MUST_USE nsresult Write(const char* aStr) + { + return nsIGZFileWriter::Write(aStr); + } + + MOZ_MUST_USE nsresult Write(const char* aStr, uint32_t aLen) + { + return nsIGZFileWriter::Write(aStr, aLen); + } + +private: + Operation mMode; + bool mInitialized; + bool mFinished; + gzFile mGZFile; +}; + +#endif diff --git a/xpcom/base/nsIConsoleListener.idl b/xpcom/base/nsIConsoleListener.idl new file mode 100644 index 000000000..45e6fcb1f --- /dev/null +++ b/xpcom/base/nsIConsoleListener.idl @@ -0,0 +1,18 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Used by the console service to notify listeners of new console messages. + */ + +#include "nsISupports.idl" + +interface nsIConsoleMessage; + +[scriptable, function, uuid(35c400a4-5792-438c-b915-65e30d58d557)] +interface nsIConsoleListener : nsISupports +{ + void observe(in nsIConsoleMessage aMessage); +}; diff --git a/xpcom/base/nsIConsoleMessage.idl b/xpcom/base/nsIConsoleMessage.idl new file mode 100644 index 000000000..bf233b28b --- /dev/null +++ b/xpcom/base/nsIConsoleMessage.idl @@ -0,0 +1,43 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * This is intended as a base interface; implementations may want to + * provide an object that can be qi'ed to provide more specific + * message information. + */ +[scriptable, uuid(3aba9617-10e2-4839-83ae-2e6fc4df428b)] +interface nsIConsoleMessage : nsISupports +{ + /** Log level constants. */ + const uint32_t debug = 0; + const uint32_t info = 1; + const uint32_t warn = 2; + const uint32_t error = 3; + + /** + * The log level of this message. + */ + readonly attribute uint32_t logLevel; + + /** + * The time (in milliseconds from the Epoch) that the message instance + * was initialised. + * The timestamp is initialized as JS_now/1000 so that it can be + * compared to Date.now in Javascript. + */ + readonly attribute long long timeStamp; + + [binaryname(MessageMoz)] readonly attribute wstring message; + + AUTF8String toString(); +}; + +%{ C++ +#define NS_CONSOLEMESSAGE_CID \ +{ 0x024efc9e, 0x54dc, 0x4844, { 0x80, 0x4b, 0x41, 0xd3, 0xf3, 0x69, 0x90, 0x73 }} +%} diff --git a/xpcom/base/nsIConsoleService.idl b/xpcom/base/nsIConsoleService.idl new file mode 100644 index 000000000..fb9e906fb --- /dev/null +++ b/xpcom/base/nsIConsoleService.idl @@ -0,0 +1,56 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIConsoleListener; +interface nsIConsoleMessage; + +[scriptable, uuid(0eb81d20-c37e-42d4-82a8-ca9ae96bdf52)] +interface nsIConsoleService : nsISupports +{ + void logMessage(in nsIConsoleMessage message); + + /** + * Convenience method for logging simple messages. + */ + void logStringMessage(in wstring message); + + /** + * Get an array of all the messages logged so far. If no messages + * are logged, this function will return a count of 0, but still + * will allocate one word for messages, so as to show up as a + * 0-length array when called from script. + */ + void getMessageArray([optional] out uint32_t count, + [retval, array, size_is(count)] out nsIConsoleMessage messages); + + /** + * To guard against stack overflows from listeners that could log + * messages (it's easy to do this inadvertently from listeners + * implemented in JavaScript), we don't call any listeners when + * another error is already being logged. + */ + void registerListener(in nsIConsoleListener listener); + + /** + * Each registered listener should also be unregistered. + */ + void unregisterListener(in nsIConsoleListener listener); + + /** + * Clear the message buffer (e.g. for privacy reasons). + */ + void reset(); +}; + + +%{ C++ +#define NS_CONSOLESERVICE_CID \ +{ 0x7e3ff85c, 0x1dd2, 0x11b2, { 0x8d, 0x4b, 0xeb, 0x45, 0x2c, 0xb0, 0xff, 0x40 }} + +#define NS_CONSOLESERVICE_CONTRACTID "@mozilla.org/consoleservice;1" +%} + diff --git a/xpcom/base/nsICycleCollectorListener.idl b/xpcom/base/nsICycleCollectorListener.idl new file mode 100644 index 000000000..cfdf9abe9 --- /dev/null +++ b/xpcom/base/nsICycleCollectorListener.idl @@ -0,0 +1,164 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +%{C++ +#include <stdio.h> + +class nsCycleCollectorLogger; +%} + +[ptr] native FILE(FILE); +[ptr] native nsCycleCollectorLoggerPtr (nsCycleCollectorLogger); +interface nsIFile; + +/** + * A set of interfaces for recording the cycle collector's work. An instance + * of @mozilla.org/cycle-collector-logger;1 can be configured to enable various + * options, then passed to the cycle collector when it runs. + * Note that additional logging options are available by setting environment + * variables, as described at the top of nsCycleCollector.cpp. + */ + +/** + * nsICycleCollectorHandler is the interface JS code should implement to + * receive the results logged by a @mozilla.org/cycle-collector-logger;1 + * instance. Pass an instance of this to the logger's 'processNext' method + * after the collection has run. This will describe the objects the cycle + * collector visited, the edges it found, and the conclusions it reached + * about the liveness of objects. + * + * In more detail: + * - For each node in the graph: + * - a call is made to either |noteRefCountedObject| or |noteGCedObject|, to + * describe the node itself; and + * - for each edge starting at that node, a call is made to |noteEdge|. + * + * - Then, a series of calls are made to: + * - |describeRoot|, for reference-counted nodes that the CC has identified as + * being alive because there are unknown references to those nodes. + * - |describeGarbage|, for nodes the cycle collector has identified as garbage. + * + * Any node not mentioned in a call to |describeRoot| or |describeGarbage| is + * neither a root nor garbage. The cycle collector was able to find all of the + * edges implied by the node's reference count. + */ +[scriptable, uuid(7f093367-1492-4b89-87af-c01dbc831246)] +interface nsICycleCollectorHandler : nsISupports +{ + void noteRefCountedObject(in ACString aAddress, + in unsigned long aRefCount, + in ACString aObjectDescription); + void noteGCedObject(in ACString aAddress, + in boolean aMarked, + in ACString aObjectDescription, + in ACString aCompartmentAddress); + void noteEdge(in ACString aFromAddress, + in ACString aToAddress, + in ACString aEdgeName); + void describeRoot(in ACString aAddress, + in unsigned long aKnownEdges); + void describeGarbage(in ACString aAddress); +}; + + +/** + * This interface allows replacing the log-writing backend for an + * nsICycleCollectorListener. As this interface is also called while + * the cycle collector is running, it cannot be implemented in JS. + */ +[scriptable, builtinclass, uuid(3ad9875f-d0e4-4ac2-87e3-f127f6c02ce1)] +interface nsICycleCollectorLogSink : nsISupports +{ + [noscript] void open(out FILE aGCLog, out FILE aCCLog); + void closeGCLog(); + void closeCCLog(); + + // This string will appear somewhere in the log's filename. + attribute AString filenameIdentifier; + + // This is the process ID; it can be changed if logging is on behalf + // of another process. + attribute int32_t processIdentifier; + + // The GC log file, if logging to files. + readonly attribute nsIFile gcLog; + + // The CC log file, if logging to files. + readonly attribute nsIFile ccLog; +}; + + +/** + * This interface is used to configure some reporting options for the cycle + * collector. This interface cannot be implemented by JavaScript code, as it + * is called while the cycle collector is running. + * + * To analyze cycle collection data in JS: + * + * - Create an instance of @mozilla.org/cycle-collector-logger;1, which + * implements this interface. + * + * - Set its |disableLog| property to true. This prevents the logger from + * printing messages about each method call to a temporary log file. + * + * - Set its |wantAfterProcessing| property to true. This tells the logger + * to record calls to its methods in memory. The |processNext| method + * returns events from this record. + * + * - Perform a collection using the logger. For example, call + * |nsIDOMWindowUtils|'s |garbageCollect| method, passing the logger as + * the |aListener| argument. + * + * - When the collection is complete, loop calling the logger's + * |processNext| method, passing a JavaScript object that implements + * nsICycleCollectorHandler. This JS code is free to allocate and operate + * on objects however it pleases: the cycle collector has finished its + * work, and the JS code is simply consuming recorded data. + */ +[scriptable, builtinclass, uuid(703b53b6-24f6-40c6-9ea9-aeb2dc53d170)] +interface nsICycleCollectorListener : nsISupports +{ + // Return a listener that directs the cycle collector to traverse + // objects that it knows won't be collectable. + // + // Note that even this listener will not visit every node in the heap; + // the cycle collector can't see the entire heap. But while this + // listener is in use, the collector disables some optimizations it + // normally uses to avoid certain classes of objects that are certainly + // alive. So, if your purpose is to get a view of the portion of the + // heap that is of interest to the cycle collector, and not simply find + // garbage, then you should use the listener this returns. + // + // Note that this does not necessarily return a new listener; rather, it may + // simply set a flag on this listener (a side effect!) and return it. + nsICycleCollectorListener allTraces(); + + // True if this listener will behave like one returned by allTraces(). + readonly attribute boolean wantAllTraces; + + // If true, do not log each method call to a temporary file. + // Initially false. + attribute boolean disableLog; + + // If |disableLog| is false, this object will be sent the log text. + attribute nsICycleCollectorLogSink logSink; + + // If true, record all method calls in memory, to be retrieved later + // using |processNext|. Initially false. + attribute boolean wantAfterProcessing; + + // Report the next recorded event to |aHandler|, and remove it from the + // record. Return false if there isn't anything more to process. + // + // Note that we only record events to report here if our + // |wantAfterProcessing| property is true. + boolean processNext(in nsICycleCollectorHandler aHandler); + + // Return the current object as an nsCycleCollectorLogger*, which is the + // only class that should be implementing this interface. We need the + // concrete implementation type to help the GC rooting analysis. + [noscript] nsCycleCollectorLoggerPtr asLogger(); +}; diff --git a/xpcom/base/nsIDebug2.idl b/xpcom/base/nsIDebug2.idl new file mode 100644 index 000000000..4401f8a91 --- /dev/null +++ b/xpcom/base/nsIDebug2.idl @@ -0,0 +1,82 @@ +/* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* interface to expose information about calls to NS_DebugBreak */ + +#include "nsISupports.idl" + +/** + * For use by consumers in scripted languages (JavaScript, Java, Python, + * Perl, ...). + * + * @note C/C++ consumers who are planning to use the nsIDebug2 interface with + * the "@mozilla.org/xpcom;1" contract should use NS_DebugBreak from xpcom + * glue instead. + * + */ + +[scriptable, uuid(9641dc15-10fb-42e3-a285-18be90a5c10b)] +interface nsIDebug2 : nsISupports +{ + /** + * Whether XPCOM was compiled with DEBUG defined. This often + * correlates to whether other code (e.g., Firefox, XULRunner) was + * compiled with DEBUG defined. + */ + readonly attribute boolean isDebugBuild; + + /** + * The number of assertions since process start. + */ + readonly attribute long assertionCount; + + /** + * Whether a debugger is currently attached. + * Supports Windows + Mac + */ + readonly attribute bool isDebuggerAttached; + + /** + * Show an assertion and trigger nsIDebug2.break(). + * + * @param aStr assertion message + * @param aExpr expression that failed + * @param aFile file containing assertion + * @param aLine line number of assertion + */ + void assertion(in string aStr, + in string aExpr, + in string aFile, + in long aLine); + + /** + * Show a warning. + * + * @param aStr warning message + * @param aFile file containing assertion + * @param aLine line number of assertion + */ + void warning(in string aStr, + in string aFile, + in long aLine); + + /** + * Request to break into a debugger. + * + * @param aFile file containing break request + * @param aLine line number of break request + */ + void break(in string aFile, + in long aLine); + + /** + * Request the process to trigger a fatal abort. + * + * @param aFile file containing abort request + * @param aLine line number of abort request + */ + void abort(in string aFile, + in long aLine); +}; diff --git a/xpcom/base/nsIErrorService.idl b/xpcom/base/nsIErrorService.idl new file mode 100644 index 000000000..9eeea2382 --- /dev/null +++ b/xpcom/base/nsIErrorService.idl @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * nsIErrorService: This is an interim service that allows nsresult codes to be mapped to + * string bundles that can be used to look up error messages. String bundle keys can also + * be mapped. + * + * This service will eventually get replaced by extending xpidl to allow errors to be defined. + * (http://bugzilla.mozilla.org/show_bug.cgi?id=13423). + */ +[scriptable, uuid(afe1f190-a3c2-11e3-a5e2-0800200c9a66)] +interface nsIErrorService : nsISupports +{ + /** + * Registers a string bundle URL for an error module. Error modules are obtained from + * nsresult code with NS_ERROR_GET_MODULE. + */ + void registerErrorStringBundle(in short errorModule, in string stringBundleURL); + + /** + * Unregisters a string bundle URL for an error module. + */ + void unregisterErrorStringBundle(in short errorModule); + + /** + * Retrieves a string bundle URL for an error module. + */ + string getErrorStringBundle(in short errorModule); +}; + +%{C++ + +// The global nsIErrorService: +#define NS_ERRORSERVICE_NAME "Error Service" +#define NS_ERRORSERVICE_CONTRACTID "@mozilla.org/xpcom/error-service;1" +#define NS_ERRORSERVICE_CID \ +{ /* 744afd5e-5f8c-11d4-9877-00c04fa0cf4a */ \ + 0x744afd5e, \ + 0x5f8c, \ + 0x11d4, \ + {0x98, 0x77, 0x00, 0xc0, 0x4f, 0xa0, 0xcf, 0x4a} \ +} + +%} diff --git a/xpcom/base/nsIException.idl b/xpcom/base/nsIException.idl new file mode 100644 index 000000000..ff5a402ed --- /dev/null +++ b/xpcom/base/nsIException.idl @@ -0,0 +1,86 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Interfaces for representing cross-language exceptions and stack traces. + */ + + +#include "nsISupports.idl" + +[scriptable, builtinclass, uuid(28bfb2a2-5ea6-4738-918b-049dc4d51f0b)] +interface nsIStackFrame : nsISupports +{ + // see nsIProgrammingLanguage for list of language consts + readonly attribute uint32_t language; + readonly attribute AUTF8String languageName; + [implicit_jscontext] + readonly attribute AString filename; + [implicit_jscontext] + readonly attribute AString name; + // Valid line numbers begin at '1'. '0' indicates unknown. + [implicit_jscontext] + readonly attribute int32_t lineNumber; + [implicit_jscontext] + readonly attribute int32_t columnNumber; + readonly attribute AUTF8String sourceLine; + [implicit_jscontext] + readonly attribute AString asyncCause; + [implicit_jscontext] + readonly attribute nsIStackFrame asyncCaller; + [implicit_jscontext] + readonly attribute nsIStackFrame caller; + + // Returns a formatted stack string that looks like the sort of + // string that would be returned by .stack on JS Error objects. + // Only works on JS-language stack frames. + [implicit_jscontext] + readonly attribute AString formattedStack; + + // Returns the underlying SavedFrame object for native JavaScript stacks, + // or null if this is not a native JavaScript stack frame. + readonly attribute jsval nativeSavedFrame; + + [implicit_jscontext] + AUTF8String toString(); +}; + +[scriptable, builtinclass, uuid(4371b5bf-6845-487f-8d9d-3f1e4a9badd2)] +interface nsIException : nsISupports +{ + // A custom message set by the thrower. + [binaryname(MessageMoz)] readonly attribute AUTF8String message; + // The nsresult associated with this exception. + readonly attribute nsresult result; + // The name of the error code (ie, a string repr of |result|) + readonly attribute AUTF8String name; + + // Filename location. This is the location that caused the + // error, which may or may not be a source file location. + // For example, standard language errors would generally have + // the same location as their top stack entry. File + // parsers may put the location of the file they were parsing, + // etc. + + // null indicates "no data" + [implicit_jscontext] + readonly attribute AString filename; + // Valid line numbers begin at '1'. '0' indicates unknown. + [implicit_jscontext] + readonly attribute uint32_t lineNumber; + // Valid column numbers begin at 0. + // We don't have an unambiguous indicator for unknown. + readonly attribute uint32_t columnNumber; + + // A stack trace, if available. + readonly attribute nsIStackFrame location; + + // Arbitary data for the implementation. + readonly attribute nsISupports data; + + // A generic formatter - make it suitable to print, etc. + [implicit_jscontext] + AUTF8String toString(); +}; diff --git a/xpcom/base/nsIGZFileWriter.idl b/xpcom/base/nsIGZFileWriter.idl new file mode 100644 index 000000000..11e27f5c4 --- /dev/null +++ b/xpcom/base/nsIGZFileWriter.idl @@ -0,0 +1,82 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +%{C++ +#include "nsDependentString.h" +#include <stdio.h> +%} + +interface nsIFile; +[ptr] native FILE(FILE); + +/** + * A simple interface for writing to a .gz file. + * + * Note that the file that this interface produces has a different format than + * what you'd get if you compressed your data as a gzip stream and dumped the + * result to a file. + * + * The standard gunzip tool cannot decompress a raw gzip stream, but can handle + * the files produced by this interface. + */ +[scriptable, uuid(6bd5642c-1b90-4499-ba4b-199f27efaba5)] +interface nsIGZFileWriter : nsISupports +{ + /** + * Initialize this object. We'll write our gzip'ed data to the given file, + * overwriting its contents if the file exists. + * + * init() will return an error if called twice. It's an error to call any + * other method on this interface without first calling init(). + */ + [must_use] void init(in nsIFile file); + + /** + * Alternate version of init() for use when the file is already opened; + * e.g., with a FileDescriptor passed over IPC. + */ + [noscript, must_use] void initANSIFileDesc(in FILE file); + + /** + * Write the given string to the file. + */ + [must_use] void write(in AUTF8String str); + + /* + * The following two overloads of Write() are C++ because we can't overload + * methods in XPIDL. Anyway, they don't add much functionality for JS + * callers. + */ + %{C++ + /** + * Write the given char* to the file (not including the null-terminator). + */ + MOZ_MUST_USE nsresult Write(const char* str) + { + return Write(str, strlen(str)); + } + + /** + * Write |length| bytes of |str| to the file. + */ + MOZ_MUST_USE nsresult Write(const char* str, uint32_t len) + { + return Write(nsDependentCString(str, len)); + } + %} + + /** + * Close this nsIGZFileWriter. Classes implementing nsIGZFileWriter will run + * this method when the underlying object is destroyed, so it's not strictly + * necessary to explicitly call it from your code. + * + * It's an error to call this method twice, and it's an error to call write() + * after finish() has been called. + */ + void finish(); +}; diff --git a/xpcom/base/nsIID.h b/xpcom/base/nsIID.h new file mode 100644 index 000000000..f2d788576 --- /dev/null +++ b/xpcom/base/nsIID.h @@ -0,0 +1,10 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __nsIID_h +#define __nsIID_h +#include "nsID.h" +#endif /* __nsIID_h */ diff --git a/xpcom/base/nsIInterfaceRequestor.idl b/xpcom/base/nsIInterfaceRequestor.idl new file mode 100644 index 000000000..9727c53cb --- /dev/null +++ b/xpcom/base/nsIInterfaceRequestor.idl @@ -0,0 +1,36 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * The nsIInterfaceRequestor interface defines a generic interface for + * requesting interfaces that a given object might provide access to. + * This is very similar to QueryInterface found in nsISupports. + * The main difference is that interfaces returned from GetInterface() + * are not required to provide a way back to the object implementing this + * interface. The semantics of QI() dictate that given an interface A that + * you QI() on to get to interface B, you must be able to QI on B to get back + * to A. This interface however allows you to obtain an interface C from A + * that may or most likely will not have the ability to get back to A. + */ + +[scriptable, uuid(033A1470-8B2A-11d3-AF88-00A024FFC08C)] +interface nsIInterfaceRequestor : nsISupports +{ + /** + * Retrieves the specified interface pointer. + * + * @param uuid The IID of the interface being requested. + * @param result [out] The interface pointer to be filled in if + * the interface is accessible. + * @throws NS_NOINTERFACE - interface not accessible. + * @throws NS_ERROR* - method failure. + */ + void getInterface(in nsIIDRef uuid, + [iid_is(uuid),retval] out nsQIResult result); +}; + diff --git a/xpcom/base/nsIMacUtils.idl b/xpcom/base/nsIMacUtils.idl new file mode 100644 index 000000000..9a60df47c --- /dev/null +++ b/xpcom/base/nsIMacUtils.idl @@ -0,0 +1,32 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * nsIMacUtils: Generic globally-available Mac-specific utilities. + */ + +[scriptable, uuid(5E9072D7-FF95-455E-9466-8AF9841A72EC)] +interface nsIMacUtils : nsISupports +{ + /** + * True when the main executable is a fat file supporting at least + * ppc and x86 (universal binary). + */ + readonly attribute boolean isUniversalBinary; + + /** + * Returns a string containing a list of architectures delimited + * by "-". Architecture sets are always in the same order: + * ppc > i386 > ppc64 > x86_64 > (future additions) + */ + readonly attribute AString architecturesInBinary; + + /** + * True when running under binary translation (Rosetta). + */ + readonly attribute boolean isTranslated; +}; diff --git a/xpcom/base/nsIMemory.idl b/xpcom/base/nsIMemory.idl new file mode 100644 index 000000000..0c1a050b7 --- /dev/null +++ b/xpcom/base/nsIMemory.idl @@ -0,0 +1,78 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * + * nsIMemory: interface to allocate and deallocate memory. Also provides + * for notifications in low-memory situations. + * + * The frozen exported symbols moz_xmalloc, moz_xrealloc, and free + * provide a more efficient way to access XPCOM memory allocation. Using + * those symbols is preferred to using the methods on this interface. + * + * A client that wishes to be notified of low memory situations (for + * example, because the client maintains a large memory cache that + * could be released when memory is tight) should register with the + * observer service (see nsIObserverService) using the topic + * "memory-pressure". There are specific types of notications + * that can occur. These types will be passed as the |aData| + * parameter of the of the "memory-pressure" notification: + * + * "low-memory" + * This will be passed as the extra data when the pressure + * observer is being asked to flush for low-memory conditions. + * + * "low-memory-ongoing" + * This will be passed when we continue to be in a low-memory + * condition and we want to flush caches and do other cheap + * forms of memory minimization, but heavy handed approaches like + * a GC are unlikely to succeed. + * + * "-no-forward" + * This is appended to the above two parameters when the resulting + * notification should not be forwarded to the child processes. + * + * "heap-minimize" + * This will be passed as the extra data when the pressure + * observer is being asked to flush because of a heap minimize + * call. + * + * "alloc-failure" + * This will be passed as the extra data when the pressure + * observer has been asked to flush because a malloc() or + * realloc() has failed. + * + * "lowering-priority" + * This will be passed as the extra data when the priority of a child + * process is lowered. The pressure observers could take the chance to + * clear caches that could be easily regenerated. This type of + * notification only appears in child processes. + */ + +[scriptable, uuid(1e004834-6d8f-425a-bc9c-a2812ed43bb7)] +interface nsIMemory : nsISupports +{ + /** + * Attempts to shrink the heap. + * @param immediate - if true, heap minimization will occur + * immediately if the call was made on the main thread. If + * false, the flush will be scheduled to happen when the app is + * idle. + * @throws NS_ERROR_FAILURE if 'immediate' is set an the call + * was not on the application's main thread. + */ + void heapMinimize(in boolean immediate); + + /** + * This predicate can be used to determine if the platform is a "low-memory" + * platform. Callers may use this to dynamically tune their behaviour + * to favour reduced memory usage at the expense of performance. The value + * returned by this function will not change over the lifetime of the process. + */ + boolean isLowMemoryPlatform(); +}; + diff --git a/xpcom/base/nsIMemoryInfoDumper.idl b/xpcom/base/nsIMemoryInfoDumper.idl new file mode 100644 index 000000000..7e08503a4 --- /dev/null +++ b/xpcom/base/nsIMemoryInfoDumper.idl @@ -0,0 +1,162 @@ +/* -*- Mode: C++; tab-width: 50; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIFile; +interface nsICycleCollectorLogSink; + +[scriptable, function, uuid(2dea18fc-fbfa-4bf7-ad45-0efaf5495f5e)] +interface nsIFinishDumpingCallback : nsISupports +{ + void callback(in nsISupports data); +}; + +/** + * Callback interface for |dumpGCAndCCLogsToFile|, below. Note that + * these method calls can occur before |dumpGCAndCCLogsToFile| + * returns. + */ +[scriptable, uuid(dc1b2b24-65bd-441b-b6bd-cb5825a7ed14)] +interface nsIDumpGCAndCCLogsCallback : nsISupports +{ + /** + * Called whenever a process has successfully finished dumping its GC/CC logs. + * Incomplete dumps (e.g., if the child crashes or is killed due to memory + * exhaustion) are not reported. + * + * @param aGCLog The file that the GC log was written to. + * + * @param aCCLog The file that the CC log was written to. + * + * @param aIsParent indicates whether this log file pair is from the + * parent process. + */ + void onDump(in nsIFile aGCLog, + in nsIFile aCCLog, + in bool aIsParent); + + /** + * Called when GC/CC logging has finished, after all calls to |onDump|. + */ + void onFinish(); +}; + +[scriptable, builtinclass, uuid(48541b74-47ee-4a62-9557-7f4b809bda5c)] +interface nsIMemoryInfoDumper : nsISupports +{ + /** + * This dumps gzipped memory reports for this process and its child + * processes. If a file of the given name exists, it will be overwritten. + * + * @param aFilename The output file. + * + * @param aFinishDumping The callback called on completion. + * + * @param aFinishDumpingData The environment for the callback. + * + * @param aAnonymize Should the reports be anonymized? + * + * Sample output, annotated with comments for explanatory purposes. + * + * { + * // The version number of the format, which will be incremented each time + * // backwards-incompatible changes are made. A mandatory integer. + * "version": 1 + * + * // Equal to nsIMemoryReporterManager::hasMozMallocUsableSize. A + * // mandatory boolean. + * "hasMozMallocUsableSize": true, + * + * // The memory reports. A mandatory array. + * "reports": [ + * // The properties correspond to the arguments of + * // nsIHandleReportCallback::callback. Every one is mandatory. + * {"process":"Main Process (pid 12345)", "path":"explicit/foo/bar", + * "kind":1, "units":0, "amount":2000000, "description":"Foo bar."}, + * {"process":"Main Process (pid 12345)", "path":"heap-allocated", + * "kind":1, "units":0, "amount":3000000, "description":"Heap allocated."}, + * {"process":"Main Process (pid 12345)", "path":"vsize", + * "kind":1, "units":0, "amount":10000000, "description":"Vsize."} + * ] + * } + */ + void dumpMemoryReportsToNamedFile(in AString aFilename, + in nsIFinishDumpingCallback aFinishDumping, + in nsISupports aFinishDumpingData, + in boolean aAnonymize); + + /** + * Similar to dumpMemoryReportsToNamedFile, this method dumps gzipped memory + * reports for this process and its child processes to files in the tmp + * directory called memory-reports-<identifier>-<pid>.json.gz (or something + * similar, such as memory-reports-<identifier>-<pid>-1.json.gz; no existing + * file will be overwritten). + * + * If DMD is enabled, this method also dumps gzipped DMD output for this + * process and its child processes to files in the tmp directory called + * dmd-<identifier>-<pid>.txt.gz (or something similar; again, no existing + * file will be overwritten). + * + * @param aIdentifier this identifier will appear in the filename of our + * about:memory dump and those of our children. + * + * If the identifier is empty, the implementation may set it arbitrarily + * and use that new value for its own dump and the dumps of its child + * processes. For example, the implementation may set |aIdentifier| to the + * number of seconds since the epoch. + * + * @param aAnonymize Should the reports be anonymized? + * + * @param aMinimizeMemoryUsage indicates whether we should run a series of + * gc/cc's in an attempt to reduce our memory usage before collecting our + * memory report. + */ + void dumpMemoryInfoToTempDir( + in AString aIdentifier, + in boolean aAnonymize, + in boolean aMinimizeMemoryUsage); + + /** + * Dump GC and CC logs to files in the OS's temp directory (or in + * $MOZ_CC_LOG_DIRECTORY, if that environment variable is specified). + * + * @param aIdentifier If aIdentifier is non-empty, this string will appear in + * the filenames of the logs we create (both for this process and, if + * aDumpChildProcesses is true, for our child processes). + * + * If aIdentifier is empty, the implementation may set it to an + * arbitrary value; for example, it may set aIdentifier to the number + * of seconds since the epoch. + * + * @param aDumpAllTraces indicates whether we should run an all-traces CC + * log. An all-traces log visits all objects currently eligible for cycle + * collection, while a non-all-traces log avoids visiting some objects + * which we know are reachable. + * + * All-traces logs are much bigger than the alternative, but they may be + * helpful when trying to understand why a particular object is alive. For + * example, a non-traces-log will skip references held by an active + * document; if your object is being held alive by such a document, you + * probably want to see those references. + * + * @param aDumpChildProcesses indicates whether we should call + * DumpGCAndCCLogsToFile in our child processes. If so, the child processes + * will dump their children, and so on. + * + */ + void dumpGCAndCCLogsToFile(in AString aIdentifier, + in bool aDumpAllTraces, + in bool aDumpChildProcesses, + in nsIDumpGCAndCCLogsCallback aCallback); + + /** + * Like |dumpGCAndCCLogsToFile|, but sends the logs to the given log + * sink object instead of accessing the filesystem directly, and + * dumps the current process only. + */ + void dumpGCAndCCLogsToSink(in bool aDumpAllTraces, + in nsICycleCollectorLogSink aSink); +}; diff --git a/xpcom/base/nsIMemoryReporter.idl b/xpcom/base/nsIMemoryReporter.idl new file mode 100644 index 000000000..9617877df --- /dev/null +++ b/xpcom/base/nsIMemoryReporter.idl @@ -0,0 +1,581 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +%{C++ +#include <stdio.h> +%} + +interface mozIDOMWindowProxy; +interface nsIRunnable; +interface nsISimpleEnumerator; +[ptr] native FILE(FILE); + +/* + * Memory reporters measure Firefox's memory usage. They are primarily used to + * generate the about:memory page. You should read + * https://developer.mozilla.org/en-US/docs/Mozilla/Performance/Memory_reporting + * before writing a memory reporter. + */ + +[scriptable, function, uuid(62ef0e1c-dbd6-11e3-aa75-3c970e9f4238)] +interface nsIMemoryReporterCallback : nsISupports +{ + /* + * The arguments to the callback are as follows. + * + * + * |process| The name of the process containing this reporter. Each + * reporter initially has "" in this field, indicating that it applies to the + * current process. (This is true even for reporters in a child process.) + * When a reporter from a child process is copied into the main process, the + * copy has its 'process' field set appropriately. + * + * + * |path| The path that this memory usage should be reported under. Paths + * are '/'-delimited, eg. "a/b/c". + * + * Each reporter can be viewed as representing a leaf node in a tree. + * Internal nodes of the tree don't have reporters. So, for example, the + * reporters "explicit/a/b", "explicit/a/c", "explicit/d/e", and + * "explicit/d/f" define this tree: + * + * explicit + * |--a + * | |--b [*] + * | \--c [*] + * \--d + * |--e [*] + * \--f [*] + * + * Nodes marked with a [*] have a reporter. Notice that the internal + * nodes are implicitly defined by the paths. + * + * Nodes within a tree should not overlap measurements, otherwise the + * parent node measurements will be double-counted. So in the example + * above, |b| should not count any allocations counted by |c|, and vice + * versa. + * + * All nodes within each tree must have the same units. + * + * If you want to include a '/' not as a path separator, e.g. because the + * path contains a URL, you need to convert each '/' in the URL to a '\'. + * Consumers of the path will undo this change. Any other '\' character + * in a path will also be changed. This is clumsy but hasn't caused any + * problems so far. + * + * The paths of all reporters form a set of trees. Trees can be + * "degenerate", i.e. contain a single entry with no '/'. + * + * + * |kind| There are three kinds of memory reporters. + * + * - HEAP: reporters measuring memory allocated by the heap allocator, + * e.g. by calling malloc, calloc, realloc, memalign, operator new, or + * operator new[]. Reporters in this category must have units + * UNITS_BYTES. + * + * - NONHEAP: reporters measuring memory which the program explicitly + * allocated, but does not live on the heap. Such memory is commonly + * allocated by calling one of the OS's memory-mapping functions (e.g. + * mmap, VirtualAlloc, or vm_allocate). Reporters in this category + * must have units UNITS_BYTES. + * + * - OTHER: reporters which don't fit into either of these categories. + * They can have any units. + * + * The kind only matters for reporters in the "explicit" tree; + * aboutMemory.js uses it to calculate "heap-unclassified". + * + * + * |units| The units on the reporter's amount. One of the following. + * + * - BYTES: The amount contains a number of bytes. + * + * - COUNT: The amount is an instantaneous count of things currently in + * existence. For instance, the number of tabs currently open would have + * units COUNT. + * + * - COUNT_CUMULATIVE: The amount contains the number of times some event + * has occurred since the application started up. For instance, the + * number of times the user has opened a new tab would have units + * COUNT_CUMULATIVE. + * + * The amount returned by a reporter with units COUNT_CUMULATIVE must + * never decrease over the lifetime of the application. + * + * - PERCENTAGE: The amount contains a fraction that should be expressed as + * a percentage. NOTE! The |amount| field should be given a value 100x + * the actual percentage; this number will be divided by 100 when shown. + * This allows a fractional percentage to be shown even though |amount| is + * an integer. E.g. if the actual percentage is 12.34%, |amount| should + * be 1234. + * + * Values greater than 100% are allowed. + * + * + * |amount| The numeric value reported by this memory reporter. Accesses + * can fail if something goes wrong when getting the amount. + * + * + * |description| A human-readable description of this memory usage report. + */ + void callback(in ACString process, in AUTF8String path, in int32_t kind, + in int32_t units, in int64_t amount, + in AUTF8String description, in nsISupports data); +}; + +/* + * An nsIMemoryReporter reports one or more memory measurements via a + * callback function which is called once for each measurement. + * + * An nsIMemoryReporter that reports a single measurement is sometimes called a + * "uni-reporter". One that reports multiple measurements is sometimes called + * a "multi-reporter". + * + * aboutMemory.js is the most important consumer of memory reports. It + * places the following constraints on reports. + * + * - All reports within a single sub-tree must have the same units. + * + * - There may be an "explicit" tree. If present, it represents + * non-overlapping regions of memory that have been explicitly allocated with + * an OS-level allocation (e.g. mmap/VirtualAlloc/vm_allocate) or a + * heap-level allocation (e.g. malloc/calloc/operator new). Reporters in + * this tree must have kind HEAP or NONHEAP, units BYTES. + * + * It is preferred, but not required, that report descriptions use complete + * sentences (i.e. start with a capital letter and end with a period, or + * similar). + */ +[scriptable, uuid(92a36db1-46bd-4fe6-988e-47db47236d8b)] +interface nsIMemoryReporter : nsISupports +{ + /* + * Run the reporter. + * + * If |anonymize| is true, the memory reporter should anonymize any + * privacy-sensitive details in memory report paths, by replacing them with a + * string such as "<anonymized>". Anonymized memory reports may be sent + * automatically via crash reports or telemetry. + * + * The following things are considered privacy-sensitive. + * + * - Content domains and URLs, and information derived from them. + * - Content data, such as strings. + * - Details about content code, such as filenames, function names or stack + * traces. + * - Details about or data from the user's system, such as filenames. + * - Running apps. + * + * In short, anything that could identify parts of the user's browsing + * history is considered privacy-sensitive. + * + * The following thing are not considered privacy-sensitive. + * + * - Chrome domains and URLs. + * - Information about installed extensions. + */ + void collectReports(in nsIMemoryReporterCallback callback, + in nsISupports data, + in boolean anonymize); + + /* + * Kinds. See the |kind| comment in nsIMemoryReporterCallback. + */ + const int32_t KIND_NONHEAP = 0; + const int32_t KIND_HEAP = 1; + const int32_t KIND_OTHER = 2; + + /* + * Units. See the |units| comment in nsIMemoryReporterCallback. + */ + const int32_t UNITS_BYTES = 0; + const int32_t UNITS_COUNT = 1; + const int32_t UNITS_COUNT_CUMULATIVE = 2; + const int32_t UNITS_PERCENTAGE = 3; +}; + +[scriptable, function, uuid(548b3909-c04d-4ca6-8466-b8bee3837457)] +interface nsIFinishReportingCallback : nsISupports +{ + void callback(in nsISupports data); +}; + +[scriptable, builtinclass, uuid(2998574d-8993-407a-b1a5-8ad7417653e1)] +interface nsIMemoryReporterManager : nsISupports +{ + /* + * Initialize. + */ + [must_use] void init(); + + /* + * Register the given nsIMemoryReporter. The Manager service will hold a + * strong reference to the given reporter, and will be responsible for freeing + * the reporter at shutdown. You may manually unregister the reporter with + * unregisterStrongReporter() at any point. + */ + void registerStrongReporter(in nsIMemoryReporter reporter); + void registerStrongAsyncReporter(in nsIMemoryReporter reporter); + + /* + * Like registerReporter, but the Manager service will hold a weak reference + * via a raw pointer to the given reporter. The reporter should be + * unregistered before shutdown. + * You cannot register JavaScript components with this function! Always + * register your JavaScript components with registerStrongReporter(). + */ + void registerWeakReporter(in nsIMemoryReporter reporter); + void registerWeakAsyncReporter(in nsIMemoryReporter reporter); + + /* + * Unregister the given memory reporter, which must have been registered with + * registerStrongReporter(). You normally don't need to unregister your + * strong reporters, as nsIMemoryReporterManager will take care of that at + * shutdown. + */ + void unregisterStrongReporter(in nsIMemoryReporter reporter); + + /* + * Unregister the given memory reporter, which must have been registered with + * registerWeakReporter(). + */ + void unregisterWeakReporter(in nsIMemoryReporter reporter); + + /* + * These functions should only be used for testing purposes. + */ + void blockRegistrationAndHideExistingReporters(); + void unblockRegistrationAndRestoreOriginalReporters(); + void registerStrongReporterEvenIfBlocked(in nsIMemoryReporter aReporter); + + /* + * Get memory reports for the current process and all child processes. + * |handleReport| is called for each report, and |finishReporting| is called + * once all reports have been handled. + * + * |finishReporting| is called even if, for example, some child processes + * fail to report back. However, calls to this method will silently and + * immediately abort -- and |finishReporting| will not be called -- if a + * previous getReports() call is still in flight, i.e. if it has not yet + * finished invoking |finishReporting|. The silent abort is because the + * in-flight request will finish soon, and the caller would very likely just + * catch and ignore any error anyway. + * + * If |anonymize| is true, it indicates that the memory reporters should + * anonymize any privacy-sensitive data (see above). + */ + void getReports(in nsIMemoryReporterCallback handleReport, + in nsISupports handleReportData, + in nsIFinishReportingCallback finishReporting, + in nsISupports finishReportingData, + in boolean anonymize); + + /* + * As above, but: If |minimizeMemoryUsage| is true, then each process will + * minimize its memory usage (see the |minimizeMemoryUsage| method) before + * gathering its report. If DMD is enabled and |DMDDumpIdent| is non-empty + * then write a DMD report to a file in the usual temporary directory (see + * |dumpMemoryInfoToTempDir| in |nsIMemoryInfoDumper|.) + */ + [noscript] void + getReportsExtended(in nsIMemoryReporterCallback handleReport, + in nsISupports handleReportData, + in nsIFinishReportingCallback finishReporting, + in nsISupports finishReportingData, + in boolean anonymize, + in boolean minimizeMemoryUsage, + in AString DMDDumpIdent); + + /* + * As above, but if DMD is enabled and |DMDFile| is non-null then + * write a DMD report to that file and close it. + */ + [noscript] void + getReportsForThisProcessExtended(in nsIMemoryReporterCallback handleReport, + in nsISupports handleReportData, + in boolean anonymize, + in FILE DMDFile, + in nsIFinishReportingCallback finishReporting, + in nsISupports finishReportingData); + + /* + * Called by an asynchronous memory reporter upon completion. + */ + [noscript] void endReport(); + + /* + * The memory reporter manager, for the most part, treats reporters + * registered with it as a black box. However, there are some + * "distinguished" amounts (as could be reported by a memory reporter) that + * the manager provides as attributes, because they are sufficiently + * interesting that we want external code (e.g. telemetry) to be able to rely + * on them. + * + * Note that these are not reporters and so getReports() does not look at + * them. However, distinguished amounts can be embedded in a reporter. + * + * Access to these attributes can fail. In particular, some of them are not + * available on all platforms. + * + * If you add a new distinguished amount, please update + * toolkit/components/aboutmemory/tests/test_memoryReporters.xul. + * + * |vsize| (UNITS_BYTES) The virtual size, i.e. the amount of address space + * taken up. + * + * |vsizeMaxContiguous| (UNITS_BYTES) The size of the largest contiguous + * block of virtual memory. + * + * |resident| (UNITS_BYTES) The resident size (a.k.a. RSS or physical memory + * used). + * + * |residentFast| (UNITS_BYTES) This is like |resident|, but on Mac OS + * |resident| can purge pages, which is slow. It also affects the result of + * |residentFast|, and so |resident| and |residentFast| should not be used + * together. + * + * |residentPeak| (UNITS_BYTES) The peak resident size. + * + * |residentUnique| (UNITS_BYTES) The unique set size (a.k.a. USS). + * + * |heapAllocated| (UNITS_BYTES) Memory mapped by the heap allocator. + * + * |heapOverheadFraction| (UNITS_PERCENTAGE) In the heap allocator, this is + * the fraction of committed heap bytes that are overhead. Like all + * UNITS_PERCENTAGE measurements, its amount is multiplied by 100x so it can + * be represented by an int64_t. + * + * |JSMainRuntimeGCHeap| (UNITS_BYTES) Size of the main JS runtime's GC + * heap. + * + * |JSMainRuntimeTemporaryPeak| (UNITS_BYTES) Peak size of the transient + * storage in the main JSRuntime. + * + * |JSMainRuntimeCompartments{System,User}| (UNITS_COUNT) The number of + * {system,user} compartments in the main JS runtime. + * + * |imagesContentUsedUncompressed| (UNITS_BYTES) Memory used for decoded + * raster images in content. + * + * |storageSQLite| (UNITS_BYTES) Memory used by SQLite. + * + * |lowMemoryEvents{Virtual,Physical}| (UNITS_COUNT_CUMULATIVE) The number + * of low-{virtual,physical}-memory events that have occurred since the + * process started. + * + * |ghostWindows| (UNITS_COUNT) The number of ghost windows. + * + * |pageFaultsHard| (UNITS_COUNT_CUMULATIVE) The number of hard (a.k.a. + * major) page faults that have occurred since the process started. + */ + [must_use] readonly attribute int64_t vsize; + [must_use] readonly attribute int64_t vsizeMaxContiguous; + [must_use] readonly attribute int64_t resident; + [must_use] readonly attribute int64_t residentFast; + [must_use] readonly attribute int64_t residentPeak; + [must_use] readonly attribute int64_t residentUnique; + + [must_use] readonly attribute int64_t heapAllocated; + [must_use] readonly attribute int64_t heapOverheadFraction; + + [must_use] readonly attribute int64_t JSMainRuntimeGCHeap; + [must_use] readonly attribute int64_t JSMainRuntimeTemporaryPeak; + [must_use] readonly attribute int64_t JSMainRuntimeCompartmentsSystem; + [must_use] readonly attribute int64_t JSMainRuntimeCompartmentsUser; + + [must_use] readonly attribute int64_t imagesContentUsedUncompressed; + + [must_use] readonly attribute int64_t storageSQLite; + + [must_use] readonly attribute int64_t lowMemoryEventsVirtual; + [must_use] readonly attribute int64_t lowMemoryEventsPhysical; + + [must_use] readonly attribute int64_t ghostWindows; + + [must_use] readonly attribute int64_t pageFaultsHard; + + /* + * This attribute indicates if moz_malloc_usable_size() works. + */ + [infallible] readonly attribute boolean hasMozMallocUsableSize; + + /* + * These attributes indicate DMD's status. "Enabled" means enabled at + * build-time. + */ + [infallible] readonly attribute boolean isDMDEnabled; + [infallible] readonly attribute boolean isDMDRunning; + + /* + * Run a series of GC/CC's in an attempt to minimize the application's memory + * usage. When we're finished, we invoke the given runnable if it's not + * null. + */ + [must_use] void minimizeMemoryUsage(in nsIRunnable callback); + + /* + * Measure the memory that is known to be owned by this tab, split up into + * several broad categories. Note that this will be an underestimate of the + * true number, due to imperfect memory reporter coverage (corresponding to + * about:memory's "heap-unclassified"), and due to some memory shared between + * tabs not being counted. + * + * The time taken for the measurement (split into JS and non-JS parts) is + * also returned. + */ + [must_use] + void sizeOfTab(in mozIDOMWindowProxy window, + out int64_t jsObjectsSize, out int64_t jsStringsSize, + out int64_t jsOtherSize, out int64_t domSize, + out int64_t styleSize, out int64_t otherSize, + out int64_t totalSize, + out double jsMilliseconds, out double nonJSMilliseconds); +}; + +%{C++ + +#include "js/TypeDecls.h" +#include "nsStringGlue.h" +#include "nsTArray.h" + +class nsPIDOMWindowOuter; + +// nsIHandleReportCallback is a better name, but keep nsIMemoryReporterCallback +// around for backwards compatibility. +typedef nsIMemoryReporterCallback nsIHandleReportCallback; + +namespace mozilla { + +// All the following registration/unregistration functions don't use +// MOZ_MUST_USE because ignoring failures is common and reasonable. + +// Register a memory reporter. The manager service will hold a strong +// reference to this reporter. +XPCOM_API(nsresult) RegisterStrongMemoryReporter(nsIMemoryReporter* aReporter); +XPCOM_API(nsresult) RegisterStrongAsyncMemoryReporter(nsIMemoryReporter* aReporter); + +// Register a memory reporter. The manager service will hold a weak reference +// to this reporter. +XPCOM_API(nsresult) RegisterWeakMemoryReporter(nsIMemoryReporter* aReporter); +XPCOM_API(nsresult) RegisterWeakAsyncMemoryReporter(nsIMemoryReporter* aReporter); + +// Unregister a strong memory reporter. +XPCOM_API(nsresult) UnregisterStrongMemoryReporter(nsIMemoryReporter* aReporter); + +// Unregister a weak memory reporter. +XPCOM_API(nsresult) UnregisterWeakMemoryReporter(nsIMemoryReporter* aReporter); + +// The memory reporter manager provides access to several distinguished +// amounts via attributes. Some of these amounts are provided by Gecko +// components that cannot be accessed directly from XPCOM code. So we provide +// the following functions for those components to be registered with the +// manager. + +typedef int64_t (*InfallibleAmountFn)(); + +#define DECL_REGISTER_DISTINGUISHED_AMOUNT(kind, name) \ + nsresult Register##name##DistinguishedAmount(kind##AmountFn aAmountFn); +#define DECL_UNREGISTER_DISTINGUISHED_AMOUNT(name) \ + nsresult Unregister##name##DistinguishedAmount(); + +DECL_REGISTER_DISTINGUISHED_AMOUNT(Infallible, JSMainRuntimeGCHeap) +DECL_REGISTER_DISTINGUISHED_AMOUNT(Infallible, JSMainRuntimeTemporaryPeak) +DECL_REGISTER_DISTINGUISHED_AMOUNT(Infallible, JSMainRuntimeCompartmentsSystem) +DECL_REGISTER_DISTINGUISHED_AMOUNT(Infallible, JSMainRuntimeCompartmentsUser) + +DECL_REGISTER_DISTINGUISHED_AMOUNT(Infallible, ImagesContentUsedUncompressed) +DECL_UNREGISTER_DISTINGUISHED_AMOUNT(ImagesContentUsedUncompressed) + +DECL_REGISTER_DISTINGUISHED_AMOUNT(Infallible, StorageSQLite) +DECL_UNREGISTER_DISTINGUISHED_AMOUNT(StorageSQLite) + +DECL_REGISTER_DISTINGUISHED_AMOUNT(Infallible, LowMemoryEventsVirtual) +DECL_REGISTER_DISTINGUISHED_AMOUNT(Infallible, LowMemoryEventsPhysical) + +DECL_REGISTER_DISTINGUISHED_AMOUNT(Infallible, GhostWindows) + +#undef DECL_REGISTER_DISTINGUISHED_AMOUNT +#undef DECL_UNREGISTER_DISTINGUISHED_AMOUNT + +// Likewise for per-tab measurement. + +typedef MOZ_MUST_USE nsresult (*JSSizeOfTabFn)(JSObject* aObj, + size_t* aJsObjectsSize, + size_t* aJsStringSize, + size_t* aJsPrivateSize, + size_t* aJsOtherSize); +typedef MOZ_MUST_USE nsresult (*NonJSSizeOfTabFn)(nsPIDOMWindowOuter* aWindow, + size_t* aDomSize, + size_t* aStyleSize, + size_t* aOtherSize); + +nsresult RegisterJSSizeOfTab(JSSizeOfTabFn aSizeOfTabFn); +nsresult RegisterNonJSSizeOfTab(NonJSSizeOfTabFn aSizeOfTabFn); + +} + +#if defined(MOZ_DMD) +#if !defined(MOZ_MEMORY) +#error "MOZ_DMD requires MOZ_MEMORY" +#endif + +#include "DMD.h" + +#define MOZ_REPORT(ptr) mozilla::dmd::Report(ptr) +#define MOZ_REPORT_ON_ALLOC(ptr) mozilla::dmd::ReportOnAlloc(ptr) + +#else + +#define MOZ_REPORT(ptr) +#define MOZ_REPORT_ON_ALLOC(ptr) + +#endif // defined(MOZ_DMD) + +// Functions generated via this macro should be used by all traversal-based +// memory reporters. Such functions return |moz_malloc_size_of(ptr)|; this +// will always be zero on some obscure platforms. +// +// You might be wondering why we have a macro that creates multiple functions +// that differ only in their name, instead of a single MallocSizeOf function. +// It's mostly to help with DMD integration, though it sometimes also helps +// with debugging and temporary ad hoc profiling. The function name chosen +// doesn't matter greatly, but it's best to make it similar to the path used by +// the relevant memory reporter(s). +#define MOZ_DEFINE_MALLOC_SIZE_OF(fn) \ + static size_t fn(const void* aPtr) \ + { \ + MOZ_REPORT(aPtr); \ + return moz_malloc_size_of(aPtr); \ + } + +// Functions generated by the next two macros should be used by wrapping +// allocators that report heap blocks as soon as they are allocated and +// unreport them as soon as they are freed. Such allocators are used in cases +// where we have third-party code that we cannot modify. The two functions +// must always be used in tandem. +#define MOZ_DEFINE_MALLOC_SIZE_OF_ON_ALLOC(fn) \ + static size_t fn(const void* aPtr) \ + { \ + MOZ_REPORT_ON_ALLOC(aPtr); \ + return moz_malloc_size_of(aPtr); \ + } +#define MOZ_DEFINE_MALLOC_SIZE_OF_ON_FREE(fn) \ + static size_t fn(const void* aPtr) \ + { \ + return moz_malloc_size_of(aPtr); \ + } + +// This macro assumes the presence of appropriate |aHandleReport| and |aData| +// variables. The (void) is there because we should always ignore the return +// value of the callback, because callback failures aren't fatal. +#define MOZ_COLLECT_REPORT(path, kind, units, amount, description) \ + (void)aHandleReport->Callback(EmptyCString(), NS_LITERAL_CSTRING(path), \ + kind, units, amount, \ + NS_LITERAL_CSTRING(description), aData) + +%} diff --git a/xpcom/base/nsIMessageLoop.idl b/xpcom/base/nsIMessageLoop.idl new file mode 100644 index 000000000..b10d1c576 --- /dev/null +++ b/xpcom/base/nsIMessageLoop.idl @@ -0,0 +1,36 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIRunnable; + +/** + * This service allows access to the current thread's Chromium MessageLoop + * instance, with some extra sugar added. If you're calling from C++, it may + * or may not make sense for you to use this interface. If you're calling from + * JS, you don't have a choice! + * + * Right now, you can only call PostIdleTask(), and the wrath of khuey is + * stopping you from adding other methods. + * + * nsIMessageLoop's contractid is "@mozilla.org/message-loop;1". + */ +[scriptable, uuid(3E8C58E8-E52C-43E0-8E66-669CA788FF5F)] +interface nsIMessageLoop : nsISupports +{ + /** + * Posts a task to be run when this thread's message loop is idle, or after + * ensureRunsAfterMS milliseconds have elapsed. (That is, the task is + * guaranteed to run /eventually/.) + * + * Note that if the event loop is busy, we will hold a reference to the task + * until ensureRunsAfterMS milliseconds have elapsed. Be careful when + * specifying long timeouts and tasks which hold references to windows or + * other large objects, because you can leak memory in a difficult-to-detect + * way! + */ + void postIdleTask(in nsIRunnable task, in uint32_t ensureRunsAfterMS); +}; diff --git a/xpcom/base/nsIMutable.idl b/xpcom/base/nsIMutable.idl new file mode 100644 index 000000000..c5c7baab0 --- /dev/null +++ b/xpcom/base/nsIMutable.idl @@ -0,0 +1,22 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * nsIMutable defines an interface to be implemented by objects which + * can be made immutable. + */ +[scriptable, uuid(321578d0-03c1-4d95-8821-021ac612d18d)] +interface nsIMutable : nsISupports +{ + /** + * Control whether or not this object can be modified. If the flag is + * false, no modification is allowed. Once the flag has been set to false, + * it cannot be reset back to true -- attempts to do so throw + * NS_ERROR_INVALID_ARG. + */ + attribute boolean mutable; +}; diff --git a/xpcom/base/nsIProgrammingLanguage.idl b/xpcom/base/nsIProgrammingLanguage.idl new file mode 100644 index 000000000..57621168b --- /dev/null +++ b/xpcom/base/nsIProgrammingLanguage.idl @@ -0,0 +1,25 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * Legacy constants for specifying programming languages. + * + * JAVASCRIPT is needed to avoid breaking addons that use it in nsIClassInfo + * to define fields that are no longer needed. + * + * UNKNOWN and JAVASCRIPT are also used in implementations of + * nsIStackFrame::language. + */ + +[scriptable, uuid(02ad9f22-3c98-46f3-be4e-2f5c9299e29a)] +interface nsIProgrammingLanguage : nsISupports +{ + const uint32_t UNKNOWN = 0; + // 1 is unused. + const uint32_t JAVASCRIPT = 2; +}; diff --git a/xpcom/base/nsISecurityConsoleMessage.idl b/xpcom/base/nsISecurityConsoleMessage.idl new file mode 100644 index 000000000..917968d83 --- /dev/null +++ b/xpcom/base/nsISecurityConsoleMessage.idl @@ -0,0 +1,20 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/* + * Holds localization message tag and message category + * for security related console messages. + */ +[uuid(FE9FC9B6-DDE2-11E2-A8F1-0A326188709B)] +interface nsISecurityConsoleMessage : nsISupports +{ + attribute AString tag; + attribute AString category; +}; + +%{ C++ +#define NS_SECURITY_CONSOLE_MESSAGE_CONTRACTID "@mozilla.org/securityconsole/message;1" +%} diff --git a/xpcom/base/nsISizeOf.h b/xpcom/base/nsISizeOf.h new file mode 100644 index 000000000..35f5de44c --- /dev/null +++ b/xpcom/base/nsISizeOf.h @@ -0,0 +1,35 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsISizeOf_h___ +#define nsISizeOf_h___ + +#include "mozilla/MemoryReporting.h" +#include "nsISupports.h" + +#define NS_ISIZEOF_IID \ + {0x61d05579, 0xd7ec, 0x485c, \ + { 0xa4, 0x0c, 0x31, 0xc7, 0x9a, 0x5c, 0xf9, 0xf3 }} + +class nsISizeOf : public nsISupports +{ +public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_ISIZEOF_IID) + + /** + * Measures the size of the things pointed to by the object. + */ + virtual size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const = 0; + + /** + * Like SizeOfExcludingThis, but also includes the size of the object itself. + */ + virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const = 0; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsISizeOf, NS_ISIZEOF_IID) + +#endif /* nsISizeOf_h___ */ diff --git a/xpcom/base/nsIStatusReporter.idl b/xpcom/base/nsIStatusReporter.idl new file mode 100644 index 000000000..9f9245f49 --- /dev/null +++ b/xpcom/base/nsIStatusReporter.idl @@ -0,0 +1,90 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsISimpleEnumerator; + +/* + * Status reporters show Firefox's service status. + */ + +[scriptable, uuid(ffcb716c-deeb-44ea-9d9d-ab25dc6980a8)] +interface nsIStatusReporter : nsISupports +{ + readonly attribute ACString name; + /* + * The name of the process containing this reporter. Each reporter initially + * has "" in this field, indicating that it applies to the current process. + */ + readonly attribute ACString process; + /* + * A human-readable status description. + */ + readonly attribute AUTF8String description; +}; + +[scriptable, uuid(fd531273-3319-4fcd-90f2-9f53876c3828)] +interface nsIStatusReporterManager : nsISupports +{ + + /* + * Return an enumerator of nsIStatusReporters that are currently registered. + */ + nsISimpleEnumerator enumerateReporters(); + + /* + * Register the given nsIStatusReporter. After a reporter is registered, + * it will be available via enumerateReporters(). The Manager service + * will hold a strong reference to the given reporter. + */ + void registerReporter(in nsIStatusReporter reporter); + + /* + * Unregister the given status reporter. + */ + void unregisterReporter(in nsIStatusReporter reporter); + + /* + * Initialize. + */ + void init(); + + /* + * Dump service status as a json file + */ + void dumpReports(); +}; + +%{C++ + +/* + * Note that this defaults 'process' to "", which is usually what's desired. + */ +#define NS_STATUS_REPORTER_IMPLEMENT(_classname, _name, _desc_Function) \ + class StatusReporter_##_classname final : public nsIStatusReporter { \ + ~StatusReporter_##_classname() {} \ + public: \ + NS_DECL_ISUPPORTS \ + NS_IMETHOD GetName(nsACString &name) override \ + { name.AssignLiteral(_name); return NS_OK; } \ + NS_IMETHOD GetProcess(nsACString &process) override \ + { process.Truncate(); return NS_OK; } \ + NS_IMETHOD GetDescription(nsACString &desc) override \ + { _desc_Function(desc); return NS_OK; } \ + }; \ + NS_IMPL_ISUPPORTS(StatusReporter_##_classname, nsIStatusReporter) + +#define NS_STATUS_REPORTER_NAME(_classname) StatusReporter_##_classname + +nsresult NS_RegisterStatusReporter(nsIStatusReporter *reporter); +nsresult NS_UnregisterStatusReporter(nsIStatusReporter *reporter); +nsresult NS_DumpStatusReporter(); + +#define NS_STATUS_REPORTER_MANAGER_CID \ +{ 0xe8eb4e7e, 0xf2cf, 0x45e5, \ +{ 0xb8, 0xa4, 0x6a, 0x0f, 0x50, 0x18, 0x84, 0x63 } } +%} diff --git a/xpcom/base/nsISupports.idl b/xpcom/base/nsISupports.idl new file mode 100644 index 000000000..45ea44ecb --- /dev/null +++ b/xpcom/base/nsISupports.idl @@ -0,0 +1,44 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * The mother of all xpcom interfaces. + */ + +/* In order to get both the right typelib and the right header we force +* the 'real' output from xpidl to be commented out in the generated header +* and includes a copy of the original nsISupports.h. This is all just to deal +* with the Mac specific ": public __comobject" thing. +*/ + +#include "nsrootidl.idl" + +%{C++ +/* + * Start commenting out the C++ versions of the below in the output header + */ +#if 0 +%} + +[scriptable, uuid(00000000-0000-0000-c000-000000000046)] +interface nsISupports { + void QueryInterface(in nsIIDRef uuid, + [iid_is(uuid),retval] out nsQIResult result); + [noscript, notxpcom] nsrefcnt AddRef(); + [noscript, notxpcom] nsrefcnt Release(); +}; + +%{C++ +/* + * End commenting out the C++ versions of the above in the output header + */ +#endif +%} + + +%{C++ +#include "nsISupportsBase.h" +#include "nsISupportsUtils.h" +%} diff --git a/xpcom/base/nsISupportsBase.h b/xpcom/base/nsISupportsBase.h new file mode 100644 index 000000000..bf5518b81 --- /dev/null +++ b/xpcom/base/nsISupportsBase.h @@ -0,0 +1,85 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +// IWYU pragma: private, include "nsISupports.h" + +#ifndef nsISupportsBase_h__ +#define nsISupportsBase_h__ + +#ifndef nscore_h___ +#include "nscore.h" +#endif + +#ifndef nsID_h__ +#include "nsID.h" +#endif + + +/*@{*/ +/** + * IID for the nsISupports interface + * {00000000-0000-0000-c000-000000000046} + * + * To maintain binary compatibility with COM's IUnknown, we define the IID + * of nsISupports to be the same as that of COM's IUnknown. + */ +#define NS_ISUPPORTS_IID \ + { 0x00000000, 0x0000, 0x0000, \ + {0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46} } + +/** + * Basic component object model interface. Objects which implement + * this interface support runtime interface discovery (QueryInterface) + * and a reference counted memory model (AddRef/Release). This is + * modelled after the win32 IUnknown API. + */ +class NS_NO_VTABLE nsISupports +{ +public: + + NS_DECLARE_STATIC_IID_ACCESSOR(NS_ISUPPORTS_IID) + + /** + * @name Methods + */ + + //@{ + /** + * A run time mechanism for interface discovery. + * @param aIID [in] A requested interface IID + * @param aInstancePtr [out] A pointer to an interface pointer to + * receive the result. + * @return <b>NS_OK</b> if the interface is supported by the associated + * instance, <b>NS_NOINTERFACE</b> if it is not. + * + * aInstancePtr must not be null. + */ + NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) = 0; + /** + * Increases the reference count for this interface. + * The associated instance will not be deleted unless + * the reference count is returned to zero. + * + * @return The resulting reference count. + */ + NS_IMETHOD_(MozExternalRefCountType) AddRef(void) = 0; + + /** + * Decreases the reference count for this interface. + * Generally, if the reference count returns to zero, + * the associated instance is deleted. + * + * @return The resulting reference count. + */ + NS_IMETHOD_(MozExternalRefCountType) Release(void) = 0; + + //@} +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsISupports, NS_ISUPPORTS_IID) + +/*@}*/ + +#endif diff --git a/xpcom/base/nsIUUIDGenerator.idl b/xpcom/base/nsIUUIDGenerator.idl new file mode 100644 index 000000000..8715ed622 --- /dev/null +++ b/xpcom/base/nsIUUIDGenerator.idl @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 50; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +[ptr] native nsNonConstIDPtr(nsID); + +/** + * nsIUUIDGenerator is implemented by a service that can generate + * universally unique identifiers, ideally using any platform-native + * method for generating UUIDs. + */ +[scriptable, uuid(138ad1b2-c694-41cc-b201-333ce936d8b8)] +interface nsIUUIDGenerator : nsISupports +{ + /** + * Obtains a new UUID using appropriate platform-specific methods to + * obtain a nsID that can be considered to be globally unique. + * + * @returns an nsID filled in with a new UUID. + * + * @throws NS_ERROR_FAILURE if a UUID cannot be generated (e.g. if + * an underlying source of randomness is not available) + */ + nsIDPtr generateUUID(); + + /** + * Obtain a new UUID like the generateUUID method, but place it in + * the provided nsID pointer instead of allocating a new nsID. + * + * @param id an existing nsID pointer where the UUID will be stored. + * + * @throws NS_ERROR_FAILURE if a UUID cannot be generated (e.g. if + * an underlying source of randomness is not available) + */ + [noscript] void generateUUIDInPlace(in nsNonConstIDPtr id); +}; diff --git a/xpcom/base/nsIVersionComparator.idl b/xpcom/base/nsIVersionComparator.idl new file mode 100644 index 000000000..1a33cb74e --- /dev/null +++ b/xpcom/base/nsIVersionComparator.idl @@ -0,0 +1,49 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * Version strings are dot-separated sequences of version-parts. + * + * A version-part consists of up to four parts, all of which are optional: + * + * <number-a><string-b><number-c><string-d (everything else)> + * + * A version-part may also consist of a single asterisk "*" which indicates + * "infinity". + * + * Numbers are base-10, and are zero if left out. + * Strings are compared bytewise. + * + * For additional backwards compatibility, if "string-b" is "+" then + * "number-a" is incremented by 1 and "string-b" becomes "pre". + * + * 1.0pre1 + * < 1.0pre2 + * < 1.0 == 1.0.0 == 1.0.0.0 + * < 1.1pre == 1.1pre0 == 1.0+ + * < 1.1pre1a + * < 1.1pre1 + * < 1.1pre10a + * < 1.1pre10 + * + * Although not required by this interface, it is recommended that + * numbers remain within the limits of a signed char, i.e. -127 to 128. + */ +[scriptable, uuid(e6cd620a-edbb-41d2-9e42-9a2ffc8107f3)] +interface nsIVersionComparator : nsISupports +{ + /** + * Compare two version strings + * @param A The first version + * @param B The second version + * @returns < 0 if A < B + * = 0 if A == B + * > 0 if A > B + */ + long compare(in ACString A, in ACString B); +}; + diff --git a/xpcom/base/nsIWeakReference.idl b/xpcom/base/nsIWeakReference.idl new file mode 100644 index 000000000..73390b15f --- /dev/null +++ b/xpcom/base/nsIWeakReference.idl @@ -0,0 +1,74 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +%{C++ +#include "mozilla/MemoryReporting.h" +%} + +/** + * An instance of |nsIWeakReference| is a proxy object that cooperates with + * its referent to give clients a non-owning, non-dangling reference. Clients + * own the proxy, and should generally manage it with an |nsCOMPtr| (see the + * type |nsWeakPtr| for a |typedef| name that stands out) as they would any + * other XPCOM object. The |QueryReferent| member function provides a + * (hopefully short-lived) owning reference on demand, through which clients + * can get useful access to the referent, while it still exists. + * + * @version 1.0 + * @see nsISupportsWeakReference + * @see nsWeakReference + * @see nsWeakPtr + */ +[scriptable, uuid(9188bc85-f92e-11d2-81ef-0060083a0bcf)] +interface nsIWeakReference : nsISupports + { + /** + * |QueryReferent| queries the referent, if it exists, and like |QueryInterface|, produces + * an owning reference to the desired interface. It is designed to look and act exactly + * like (a proxied) |QueryInterface|. Don't hold on to the produced interface permanently; + * that would defeat the purpose of using a non-owning |nsIWeakReference| in the first place. + */ + void QueryReferent( in nsIIDRef uuid, [iid_is(uuid), retval] out nsQIResult result ); + +%{C++ + virtual size_t SizeOfOnlyThis(mozilla::MallocSizeOf aMallocSizeOf) const = 0; +%} + }; + + +/** + * |nsISupportsWeakReference| is a factory interface which produces appropriate + * instances of |nsIWeakReference|. Weak references in this scheme can only be + * produced for objects that implement this interface. + * + * @version 1.0 + * @see nsIWeakReference + * @see nsSupportsWeakReference + */ +[scriptable, uuid(9188bc86-f92e-11d2-81ef-0060083a0bcf)] +interface nsISupportsWeakReference : nsISupports + { + /** + * |GetWeakReference| produces an appropriate instance of |nsIWeakReference|. + * As with all good XPCOM `getters', you own the resulting interface and should + * manage it with an |nsCOMPtr|. + * + * @see nsIWeakReference + * @see nsWeakPtr + * @see nsCOMPtr + */ + nsIWeakReference GetWeakReference(); + }; + + +%{C++ +#ifdef MOZILLA_INTERNAL_API +#include "nsIWeakReferenceUtils.h" +#endif +%} + diff --git a/xpcom/base/nsInterfaceRequestorAgg.cpp b/xpcom/base/nsInterfaceRequestorAgg.cpp new file mode 100644 index 000000000..7e5cd83da --- /dev/null +++ b/xpcom/base/nsInterfaceRequestorAgg.cpp @@ -0,0 +1,86 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsInterfaceRequestorAgg.h" +#include "nsIInterfaceRequestor.h" +#include "nsCOMPtr.h" +#include "mozilla/Attributes.h" +#include "nsThreadUtils.h" +#include "nsProxyRelease.h" + +class nsInterfaceRequestorAgg final : public nsIInterfaceRequestor +{ +public: + // XXX This needs to support threadsafe refcounting until we fix bug 243591. + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINTERFACEREQUESTOR + + nsInterfaceRequestorAgg(nsIInterfaceRequestor* aFirst, + nsIInterfaceRequestor* aSecond, + nsIEventTarget* aConsumerTarget = nullptr) + : mFirst(aFirst) + , mSecond(aSecond) + , mConsumerTarget(aConsumerTarget) + { + if (!mConsumerTarget) { + mConsumerTarget = NS_GetCurrentThread(); + } + } + +private: + ~nsInterfaceRequestorAgg(); + + nsCOMPtr<nsIInterfaceRequestor> mFirst, mSecond; + nsCOMPtr<nsIEventTarget> mConsumerTarget; +}; + +NS_IMPL_ISUPPORTS(nsInterfaceRequestorAgg, nsIInterfaceRequestor) + +NS_IMETHODIMP +nsInterfaceRequestorAgg::GetInterface(const nsIID& aIID, void** aResult) +{ + nsresult rv = NS_ERROR_NO_INTERFACE; + if (mFirst) { + rv = mFirst->GetInterface(aIID, aResult); + } + if (mSecond && NS_FAILED(rv)) { + rv = mSecond->GetInterface(aIID, aResult); + } + return rv; +} + +nsInterfaceRequestorAgg::~nsInterfaceRequestorAgg() +{ + NS_ProxyRelease(mConsumerTarget, mFirst.forget()); + NS_ProxyRelease(mConsumerTarget, mSecond.forget()); +} + +nsresult +NS_NewInterfaceRequestorAggregation(nsIInterfaceRequestor* aFirst, + nsIInterfaceRequestor* aSecond, + nsIInterfaceRequestor** aResult) +{ + *aResult = new nsInterfaceRequestorAgg(aFirst, aSecond); + if (!*aResult) { + return NS_ERROR_OUT_OF_MEMORY; + } + NS_ADDREF(*aResult); + return NS_OK; +} + +nsresult +NS_NewInterfaceRequestorAggregation(nsIInterfaceRequestor* aFirst, + nsIInterfaceRequestor* aSecond, + nsIEventTarget* aTarget, + nsIInterfaceRequestor** aResult) +{ + *aResult = new nsInterfaceRequestorAgg(aFirst, aSecond, aTarget); + if (!*aResult) { + return NS_ERROR_OUT_OF_MEMORY; + } + NS_ADDREF(*aResult); + return NS_OK; +} diff --git a/xpcom/base/nsInterfaceRequestorAgg.h b/xpcom/base/nsInterfaceRequestorAgg.h new file mode 100644 index 000000000..62b4f61f9 --- /dev/null +++ b/xpcom/base/nsInterfaceRequestorAgg.h @@ -0,0 +1,38 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsInterfaceRequestorAgg_h__ +#define nsInterfaceRequestorAgg_h__ + +#include "nsError.h" + +class nsIEventTarget; +class nsIInterfaceRequestor; + +/** + * This function returns an instance of nsIInterfaceRequestor that aggregates + * two nsIInterfaceRequestor instances. Its GetInterface method queries + * aFirst for the requested interface and will query aSecond only if aFirst + * failed to supply the requested interface. Both aFirst and aSecond may + * be null, and will be released on the main thread when the aggregator is + * destroyed. + */ +extern nsresult +NS_NewInterfaceRequestorAggregation(nsIInterfaceRequestor* aFirst, + nsIInterfaceRequestor* aSecond, + nsIInterfaceRequestor** aResult); + +/** + * Like the previous method, but aFirst and aSecond will be released on the + * provided target thread. + */ +extern nsresult +NS_NewInterfaceRequestorAggregation(nsIInterfaceRequestor* aFirst, + nsIInterfaceRequestor* aSecond, + nsIEventTarget* aTarget, + nsIInterfaceRequestor** aResult); + +#endif // !defined( nsInterfaceRequestorAgg_h__ ) diff --git a/xpcom/base/nsMacUtilsImpl.cpp b/xpcom/base/nsMacUtilsImpl.cpp new file mode 100644 index 000000000..e2706047a --- /dev/null +++ b/xpcom/base/nsMacUtilsImpl.cpp @@ -0,0 +1,147 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsMacUtilsImpl.h" + +#include <CoreFoundation/CoreFoundation.h> + +NS_IMPL_ISUPPORTS(nsMacUtilsImpl, nsIMacUtils) + +nsresult +nsMacUtilsImpl::GetArchString(nsAString& aArchString) +{ + if (!mBinaryArchs.IsEmpty()) { + aArchString.Assign(mBinaryArchs); + return NS_OK; + } + + aArchString.Truncate(); + + bool foundPPC = false, + foundX86 = false, + foundPPC64 = false, + foundX86_64 = false; + + CFBundleRef mainBundle = ::CFBundleGetMainBundle(); + if (!mainBundle) { + return NS_ERROR_FAILURE; + } + + CFArrayRef archList = ::CFBundleCopyExecutableArchitectures(mainBundle); + if (!archList) { + return NS_ERROR_FAILURE; + } + + CFIndex archCount = ::CFArrayGetCount(archList); + for (CFIndex i = 0; i < archCount; i++) { + CFNumberRef arch = + static_cast<CFNumberRef>(::CFArrayGetValueAtIndex(archList, i)); + + int archInt = 0; + if (!::CFNumberGetValue(arch, kCFNumberIntType, &archInt)) { + ::CFRelease(archList); + return NS_ERROR_FAILURE; + } + + if (archInt == kCFBundleExecutableArchitecturePPC) { + foundPPC = true; + } else if (archInt == kCFBundleExecutableArchitectureI386) { + foundX86 = true; + } else if (archInt == kCFBundleExecutableArchitecturePPC64) { + foundPPC64 = true; + } else if (archInt == kCFBundleExecutableArchitectureX86_64) { + foundX86_64 = true; + } + } + + ::CFRelease(archList); + + // The order in the string must always be the same so + // don't do this in the loop. + if (foundPPC) { + mBinaryArchs.AppendLiteral("ppc"); + } + + if (foundX86) { + if (!mBinaryArchs.IsEmpty()) { + mBinaryArchs.Append('-'); + } + mBinaryArchs.AppendLiteral("i386"); + } + + if (foundPPC64) { + if (!mBinaryArchs.IsEmpty()) { + mBinaryArchs.Append('-'); + } + mBinaryArchs.AppendLiteral("ppc64"); + } + + if (foundX86_64) { + if (!mBinaryArchs.IsEmpty()) { + mBinaryArchs.Append('-'); + } + mBinaryArchs.AppendLiteral("x86_64"); + } + + aArchString.Assign(mBinaryArchs); + + return (aArchString.IsEmpty() ? NS_ERROR_FAILURE : NS_OK); +} + +NS_IMETHODIMP +nsMacUtilsImpl::GetIsUniversalBinary(bool* aIsUniversalBinary) +{ + if (NS_WARN_IF(!aIsUniversalBinary)) { + return NS_ERROR_INVALID_ARG; + } + *aIsUniversalBinary = false; + + nsAutoString archString; + nsresult rv = GetArchString(archString); + if (NS_FAILED(rv)) { + return rv; + } + + // The delimiter char in the arch string is '-', so if that character + // is in the string we know we have multiple architectures. + *aIsUniversalBinary = (archString.Find("-") > -1); + + return NS_OK; +} + +NS_IMETHODIMP +nsMacUtilsImpl::GetArchitecturesInBinary(nsAString& aArchString) +{ + return GetArchString(aArchString); +} + +// True when running under binary translation (Rosetta). +NS_IMETHODIMP +nsMacUtilsImpl::GetIsTranslated(bool* aIsTranslated) +{ +#ifdef __ppc__ + static bool sInitialized = false; + + // Initialize sIsNative to 1. If the sysctl fails because it doesn't + // exist, then translation is not possible, so the process must not be + // running translated. + static int32_t sIsNative = 1; + + if (!sInitialized) { + size_t sz = sizeof(sIsNative); + sysctlbyname("sysctl.proc_native", &sIsNative, &sz, nullptr, 0); + sInitialized = true; + } + + *aIsTranslated = !sIsNative; +#else + // Translation only exists for ppc code. Other architectures aren't + // translated. + *aIsTranslated = false; +#endif + + return NS_OK; +} diff --git a/xpcom/base/nsMacUtilsImpl.h b/xpcom/base/nsMacUtilsImpl.h new file mode 100644 index 000000000..4a480ffe5 --- /dev/null +++ b/xpcom/base/nsMacUtilsImpl.h @@ -0,0 +1,42 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsMacUtilsImpl_h___ +#define nsMacUtilsImpl_h___ + +#include "nsIMacUtils.h" +#include "nsString.h" +#include "mozilla/Attributes.h" + +class nsMacUtilsImpl final : public nsIMacUtils +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIMACUTILS + + nsMacUtilsImpl() + { + } + +private: + ~nsMacUtilsImpl() + { + } + + nsresult GetArchString(nsAString& aArchString); + + // A string containing a "-" delimited list of architectures + // in our binary. + nsString mBinaryArchs; +}; + +// Global singleton service +// 697BD3FD-43E5-41CE-AD5E-C339175C0818 +#define NS_MACUTILSIMPL_CID \ + {0x697BD3FD, 0x43E5, 0x41CE, {0xAD, 0x5E, 0xC3, 0x39, 0x17, 0x5C, 0x08, 0x18}} +#define NS_MACUTILSIMPL_CONTRACTID "@mozilla.org/xpcom/mac-utils;1" + +#endif /* nsMacUtilsImpl_h___ */ diff --git a/xpcom/base/nsMemoryImpl.cpp b/xpcom/base/nsMemoryImpl.cpp new file mode 100644 index 000000000..1d1576fbd --- /dev/null +++ b/xpcom/base/nsMemoryImpl.cpp @@ -0,0 +1,184 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsMemoryImpl.h" +#include "nsThreadUtils.h" + +#include "nsIObserver.h" +#include "nsIObserverService.h" +#include "nsISimpleEnumerator.h" + +#include "nsCOMPtr.h" +#include "mozilla/Services.h" + +#ifdef ANDROID +#include <stdio.h> + +// Minimum memory threshold for a device to be considered +// a low memory platform. This value has be in sync with +// Java's equivalent threshold, defined in +// mobile/android/base/util/HardwareUtils.java +#define LOW_MEMORY_THRESHOLD_KB (384 * 1024) +#endif + +static nsMemoryImpl sGlobalMemory; + +NS_IMPL_QUERY_INTERFACE(nsMemoryImpl, nsIMemory) + +NS_IMETHODIMP +nsMemoryImpl::HeapMinimize(bool aImmediate) +{ + return FlushMemory(u"heap-minimize", aImmediate); +} + +NS_IMETHODIMP +nsMemoryImpl::IsLowMemoryPlatform(bool* aResult) +{ +#ifdef ANDROID + static int sLowMemory = -1; // initialize to unknown, lazily evaluate to 0 or 1 + if (sLowMemory == -1) { + sLowMemory = 0; // assume "not low memory" in case file operations fail + *aResult = false; + + // check if MemTotal from /proc/meminfo is less than LOW_MEMORY_THRESHOLD_KB + FILE* fd = fopen("/proc/meminfo", "r"); + if (!fd) { + return NS_OK; + } + uint64_t mem = 0; + int rv = fscanf(fd, "MemTotal: %llu kB", &mem); + if (fclose(fd)) { + return NS_OK; + } + if (rv != 1) { + return NS_OK; + } + sLowMemory = (mem < LOW_MEMORY_THRESHOLD_KB) ? 1 : 0; + } + *aResult = (sLowMemory == 1); +#else + *aResult = false; +#endif + return NS_OK; +} + +/*static*/ nsresult +nsMemoryImpl::Create(nsISupports* aOuter, const nsIID& aIID, void** aResult) +{ + if (NS_WARN_IF(aOuter)) { + return NS_ERROR_NO_AGGREGATION; + } + return sGlobalMemory.QueryInterface(aIID, aResult); +} + +nsresult +nsMemoryImpl::FlushMemory(const char16_t* aReason, bool aImmediate) +{ + nsresult rv = NS_OK; + + if (aImmediate) { + // They've asked us to run the flusher *immediately*. We've + // got to be on the UI main thread for us to be able to do + // that...are we? + if (!NS_IsMainThread()) { + NS_ERROR("can't synchronously flush memory: not on UI thread"); + return NS_ERROR_FAILURE; + } + } + + bool lastVal = sIsFlushing.exchange(true); + if (lastVal) { + return NS_OK; + } + + PRIntervalTime now = PR_IntervalNow(); + + // Run the flushers immediately if we can; otherwise, proxy to the + // UI thread an run 'em asynchronously. + if (aImmediate) { + rv = RunFlushers(aReason); + } else { + // Don't broadcast more than once every 1000ms to avoid being noisy + if (PR_IntervalToMicroseconds(now - sLastFlushTime) > 1000) { + sFlushEvent.mReason = aReason; + rv = NS_DispatchToMainThread(&sFlushEvent); + } + } + + sLastFlushTime = now; + return rv; +} + +nsresult +nsMemoryImpl::RunFlushers(const char16_t* aReason) +{ + nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); + if (os) { + + // Instead of: + // os->NotifyObservers(this, "memory-pressure", aReason); + // we are going to do this manually to see who/what is + // deallocating. + + nsCOMPtr<nsISimpleEnumerator> e; + os->EnumerateObservers("memory-pressure", getter_AddRefs(e)); + + if (e) { + nsCOMPtr<nsIObserver> observer; + bool loop = true; + + while (NS_SUCCEEDED(e->HasMoreElements(&loop)) && loop) { + nsCOMPtr<nsISupports> supports; + e->GetNext(getter_AddRefs(supports)); + + if (!supports) { + continue; + } + + observer = do_QueryInterface(supports); + observer->Observe(observer, "memory-pressure", aReason); + } + } + } + + sIsFlushing = false; + return NS_OK; +} + +// XXX need NS_IMPL_STATIC_ADDREF/RELEASE +NS_IMETHODIMP_(MozExternalRefCountType) +nsMemoryImpl::FlushEvent::AddRef() +{ + return 2; +} +NS_IMETHODIMP_(MozExternalRefCountType) +nsMemoryImpl::FlushEvent::Release() +{ + return 1; +} +NS_IMPL_QUERY_INTERFACE(nsMemoryImpl::FlushEvent, nsIRunnable) + +NS_IMETHODIMP +nsMemoryImpl::FlushEvent::Run() +{ + sGlobalMemory.RunFlushers(mReason); + return NS_OK; +} + +mozilla::Atomic<bool> +nsMemoryImpl::sIsFlushing; + +PRIntervalTime +nsMemoryImpl::sLastFlushTime = 0; + +nsMemoryImpl::FlushEvent +nsMemoryImpl::sFlushEvent; + +nsresult +NS_GetMemoryManager(nsIMemory** aResult) +{ + return sGlobalMemory.QueryInterface(NS_GET_IID(nsIMemory), (void**)aResult); +} diff --git a/xpcom/base/nsMemoryImpl.h b/xpcom/base/nsMemoryImpl.h new file mode 100644 index 000000000..9e2d46d38 --- /dev/null +++ b/xpcom/base/nsMemoryImpl.h @@ -0,0 +1,55 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsMemoryImpl_h__ +#define nsMemoryImpl_h__ + +#include "mozilla/Atomics.h" + +#include "nsIMemory.h" +#include "nsIRunnable.h" + +// nsMemoryImpl is a static object. We can do this because it doesn't have +// a constructor/destructor or any instance members. Please don't add +// instance member variables, only static member variables. + +class nsMemoryImpl : public nsIMemory +{ +public: + // We don't use the generic macros because we are a special static object + NS_IMETHOD QueryInterface(REFNSIID aIID, void** aResult) override; + NS_IMETHOD_(MozExternalRefCountType) AddRef(void) override + { + return 1; + } + NS_IMETHOD_(MozExternalRefCountType) Release(void) override + { + return 1; + } + + NS_DECL_NSIMEMORY + + static nsresult Create(nsISupports* aOuter, + const nsIID& aIID, void** aResult); + + nsresult FlushMemory(const char16_t* aReason, bool aImmediate); + nsresult RunFlushers(const char16_t* aReason); + +protected: + struct FlushEvent : public nsIRunnable + { + constexpr FlushEvent() : mReason(nullptr) {} + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIRUNNABLE + const char16_t* mReason; + }; + + static mozilla::Atomic<bool> sIsFlushing; + static FlushEvent sFlushEvent; + static PRIntervalTime sLastFlushTime; +}; + +#endif // nsMemoryImpl_h__ diff --git a/xpcom/base/nsMemoryInfoDumper.cpp b/xpcom/base/nsMemoryInfoDumper.cpp new file mode 100644 index 000000000..06453b126 --- /dev/null +++ b/xpcom/base/nsMemoryInfoDumper.cpp @@ -0,0 +1,830 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/JSONWriter.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/nsMemoryInfoDumper.h" +#include "mozilla/DebugOnly.h" +#include "nsDumpUtils.h" + +#include "mozilla/Unused.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/ContentChild.h" +#include "nsIConsoleService.h" +#include "nsCycleCollector.h" +#include "nsICycleCollectorListener.h" +#include "nsIMemoryReporter.h" +#include "nsDirectoryServiceDefs.h" +#include "nsGZFileWriter.h" +#include "nsJSEnvironment.h" +#include "nsPrintfCString.h" +#include "nsISimpleEnumerator.h" +#include "nsServiceManagerUtils.h" +#include "nsIFile.h" + +#ifdef XP_WIN +#include <process.h> +#ifndef getpid +#define getpid _getpid +#endif +#else +#include <unistd.h> +#endif + +#ifdef XP_UNIX +#define MOZ_SUPPORTS_FIFO 1 +#endif + +#if defined(XP_LINUX) || defined(__FreeBSD__) +#define MOZ_SUPPORTS_RT_SIGNALS 1 +#endif + +#if defined(MOZ_SUPPORTS_RT_SIGNALS) +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> +#endif + +#if defined(MOZ_SUPPORTS_FIFO) +#include "mozilla/Preferences.h" +#endif + +using namespace mozilla; +using namespace mozilla::dom; + +namespace { + +class DumpMemoryInfoToTempDirRunnable : public Runnable +{ +public: + DumpMemoryInfoToTempDirRunnable(const nsAString& aIdentifier, + bool aAnonymize, bool aMinimizeMemoryUsage) + : mIdentifier(aIdentifier) + , mAnonymize(aAnonymize) + , mMinimizeMemoryUsage(aMinimizeMemoryUsage) + { + } + + NS_IMETHOD Run() override + { + nsCOMPtr<nsIMemoryInfoDumper> dumper = + do_GetService("@mozilla.org/memory-info-dumper;1"); + dumper->DumpMemoryInfoToTempDir(mIdentifier, mAnonymize, + mMinimizeMemoryUsage); + return NS_OK; + } + +private: + const nsString mIdentifier; + const bool mAnonymize; + const bool mMinimizeMemoryUsage; +}; + +class GCAndCCLogDumpRunnable final + : public Runnable + , public nsIDumpGCAndCCLogsCallback +{ +public: + NS_DECL_ISUPPORTS_INHERITED + + GCAndCCLogDumpRunnable(const nsAString& aIdentifier, + bool aDumpAllTraces, + bool aDumpChildProcesses) + : mIdentifier(aIdentifier) + , mDumpAllTraces(aDumpAllTraces) + , mDumpChildProcesses(aDumpChildProcesses) + { + } + + NS_IMETHOD Run() override + { + nsCOMPtr<nsIMemoryInfoDumper> dumper = + do_GetService("@mozilla.org/memory-info-dumper;1"); + + dumper->DumpGCAndCCLogsToFile(mIdentifier, mDumpAllTraces, + mDumpChildProcesses, this); + return NS_OK; + } + + NS_IMETHOD OnDump(nsIFile* aGCLog, nsIFile* aCCLog, bool aIsParent) override + { + return NS_OK; + } + + NS_IMETHOD OnFinish() override + { + return NS_OK; + } + +private: + ~GCAndCCLogDumpRunnable() {} + + const nsString mIdentifier; + const bool mDumpAllTraces; + const bool mDumpChildProcesses; +}; + +NS_IMPL_ISUPPORTS_INHERITED(GCAndCCLogDumpRunnable, Runnable, + nsIDumpGCAndCCLogsCallback) + +} // namespace + +#if defined(MOZ_SUPPORTS_RT_SIGNALS) // { +namespace { + +/* + * The following code supports dumping about:memory upon receiving a signal. + * + * We listen for the following signals: + * + * - SIGRTMIN: Dump our memory reporters (and those of our child + * processes), + * - SIGRTMIN + 1: Dump our memory reporters (and those of our child + * processes) after minimizing memory usage, and + * - SIGRTMIN + 2: Dump the GC and CC logs in this and our child processes. + * + * When we receive one of these signals, we write the signal number to a pipe. + * The IO thread then notices that the pipe has been written to, and kicks off + * the appropriate task on the main thread. + * + * This scheme is similar to using signalfd(), except it's portable and it + * doesn't require the use of sigprocmask, which is problematic because it + * masks signals received by child processes. + * + * In theory, we could use Chromium's MessageLoopForIO::CatchSignal() for this. + * But that uses libevent, which does not handle the realtime signals (bug + * 794074). + */ + +// It turns out that at least on some systems, SIGRTMIN is not a compile-time +// constant, so these have to be set at runtime. +static uint8_t sDumpAboutMemorySignum; // SIGRTMIN +static uint8_t sDumpAboutMemoryAfterMMUSignum; // SIGRTMIN + 1 +static uint8_t sGCAndCCDumpSignum; // SIGRTMIN + 2 + +void doMemoryReport(const uint8_t aRecvSig) +{ + // Dump our memory reports (but run this on the main thread!). + bool minimize = aRecvSig == sDumpAboutMemoryAfterMMUSignum; + LOG("SignalWatcher(sig %d) dispatching memory report runnable.", aRecvSig); + RefPtr<DumpMemoryInfoToTempDirRunnable> runnable = + new DumpMemoryInfoToTempDirRunnable(/* identifier = */ EmptyString(), + /* anonymize = */ false, + minimize); + NS_DispatchToMainThread(runnable); +} + +void doGCCCDump(const uint8_t aRecvSig) +{ + LOG("SignalWatcher(sig %d) dispatching GC/CC log runnable.", aRecvSig); + // Dump GC and CC logs (from the main thread). + RefPtr<GCAndCCLogDumpRunnable> runnable = + new GCAndCCLogDumpRunnable(/* identifier = */ EmptyString(), + /* allTraces = */ true, + /* dumpChildProcesses = */ true); + NS_DispatchToMainThread(runnable); +} + +} // namespace +#endif // MOZ_SUPPORTS_RT_SIGNALS } + +#if defined(MOZ_SUPPORTS_FIFO) // { +namespace { + +void +doMemoryReport(const nsCString& aInputStr) +{ + bool minimize = aInputStr.EqualsLiteral("minimize memory report"); + LOG("FifoWatcher(command:%s) dispatching memory report runnable.", + aInputStr.get()); + RefPtr<DumpMemoryInfoToTempDirRunnable> runnable = + new DumpMemoryInfoToTempDirRunnable(/* identifier = */ EmptyString(), + /* anonymize = */ false, + minimize); + NS_DispatchToMainThread(runnable); +} + +void +doGCCCDump(const nsCString& aInputStr) +{ + bool doAllTracesGCCCDump = aInputStr.EqualsLiteral("gc log"); + LOG("FifoWatcher(command:%s) dispatching GC/CC log runnable.", aInputStr.get()); + RefPtr<GCAndCCLogDumpRunnable> runnable = + new GCAndCCLogDumpRunnable(/* identifier = */ EmptyString(), + doAllTracesGCCCDump, + /* dumpChildProcesses = */ true); + NS_DispatchToMainThread(runnable); +} + +bool +SetupFifo() +{ +#ifdef DEBUG + static bool fifoCallbacksRegistered = false; +#endif + + if (!FifoWatcher::MaybeCreate()) { + return false; + } + + MOZ_ASSERT(!fifoCallbacksRegistered, + "FifoWatcher callbacks should be registered only once"); + + FifoWatcher* fw = FifoWatcher::GetSingleton(); + // Dump our memory reports (but run this on the main thread!). + fw->RegisterCallback(NS_LITERAL_CSTRING("memory report"), + doMemoryReport); + fw->RegisterCallback(NS_LITERAL_CSTRING("minimize memory report"), + doMemoryReport); + // Dump GC and CC logs (from the main thread). + fw->RegisterCallback(NS_LITERAL_CSTRING("gc log"), + doGCCCDump); + fw->RegisterCallback(NS_LITERAL_CSTRING("abbreviated gc log"), + doGCCCDump); + +#ifdef DEBUG + fifoCallbacksRegistered = true; +#endif + return true; +} + +void +OnFifoEnabledChange(const char* /*unused*/, void* /*unused*/) +{ + LOG("%s changed", FifoWatcher::kPrefName); + if (SetupFifo()) { + Preferences::UnregisterCallback(OnFifoEnabledChange, + FifoWatcher::kPrefName, + nullptr); + } +} + +} // namespace +#endif // MOZ_SUPPORTS_FIFO } + +NS_IMPL_ISUPPORTS(nsMemoryInfoDumper, nsIMemoryInfoDumper) + +nsMemoryInfoDumper::nsMemoryInfoDumper() +{ +} + +nsMemoryInfoDumper::~nsMemoryInfoDumper() +{ +} + +/* static */ void +nsMemoryInfoDumper::Initialize() +{ +#if defined(MOZ_SUPPORTS_RT_SIGNALS) + SignalPipeWatcher* sw = SignalPipeWatcher::GetSingleton(); + + // Dump memory reporters (and those of our child processes) + sDumpAboutMemorySignum = SIGRTMIN; + sw->RegisterCallback(sDumpAboutMemorySignum, doMemoryReport); + // Dump our memory reporters after minimizing memory usage + sDumpAboutMemoryAfterMMUSignum = SIGRTMIN + 1; + sw->RegisterCallback(sDumpAboutMemoryAfterMMUSignum, doMemoryReport); + // Dump the GC and CC logs in this and our child processes. + sGCAndCCDumpSignum = SIGRTMIN + 2; + sw->RegisterCallback(sGCAndCCDumpSignum, doGCCCDump); +#endif + +#if defined(MOZ_SUPPORTS_FIFO) + if (!SetupFifo()) { + // NB: This gets loaded early enough that it's possible there is a user pref + // set to enable the fifo watcher that has not been loaded yet. Register + // to attempt to initialize if the fifo watcher becomes enabled by + // a user pref. + Preferences::RegisterCallback(OnFifoEnabledChange, + FifoWatcher::kPrefName, + nullptr); + } +#endif +} + +static void +EnsureNonEmptyIdentifier(nsAString& aIdentifier) +{ + if (!aIdentifier.IsEmpty()) { + return; + } + + // If the identifier is empty, set it to the number of whole seconds since the + // epoch. This identifier will appear in the files that this process + // generates and also the files generated by this process's children, allowing + // us to identify which files are from the same memory report request. + aIdentifier.AppendInt(static_cast<int64_t>(PR_Now()) / 1000000); +} + +// Use XPCOM refcounting to fire |onFinish| when all reference-holders +// (remote dump actors or the |DumpGCAndCCLogsToFile| activation itself) +// have gone away. +class nsDumpGCAndCCLogsCallbackHolder final + : public nsIDumpGCAndCCLogsCallback +{ +public: + NS_DECL_ISUPPORTS + + explicit nsDumpGCAndCCLogsCallbackHolder(nsIDumpGCAndCCLogsCallback* aCallback) + : mCallback(aCallback) + { + } + + NS_IMETHOD OnFinish() override + { + return NS_ERROR_UNEXPECTED; + } + + NS_IMETHOD OnDump(nsIFile* aGCLog, nsIFile* aCCLog, bool aIsParent) override + { + return mCallback->OnDump(aGCLog, aCCLog, aIsParent); + } + +private: + ~nsDumpGCAndCCLogsCallbackHolder() + { + Unused << mCallback->OnFinish(); + } + + nsCOMPtr<nsIDumpGCAndCCLogsCallback> mCallback; +}; + +NS_IMPL_ISUPPORTS(nsDumpGCAndCCLogsCallbackHolder, nsIDumpGCAndCCLogsCallback) + +NS_IMETHODIMP +nsMemoryInfoDumper::DumpGCAndCCLogsToFile(const nsAString& aIdentifier, + bool aDumpAllTraces, + bool aDumpChildProcesses, + nsIDumpGCAndCCLogsCallback* aCallback) +{ + nsString identifier(aIdentifier); + EnsureNonEmptyIdentifier(identifier); + nsCOMPtr<nsIDumpGCAndCCLogsCallback> callbackHolder = + new nsDumpGCAndCCLogsCallbackHolder(aCallback); + + if (aDumpChildProcesses) { + nsTArray<ContentParent*> children; + ContentParent::GetAll(children); + for (uint32_t i = 0; i < children.Length(); i++) { + ContentParent* cp = children[i]; + nsCOMPtr<nsICycleCollectorLogSink> logSink = + nsCycleCollector_createLogSink(); + + logSink->SetFilenameIdentifier(identifier); + logSink->SetProcessIdentifier(cp->Pid()); + + Unused << cp->CycleCollectWithLogs(aDumpAllTraces, logSink, + callbackHolder); + } + } + + nsCOMPtr<nsICycleCollectorListener> logger = + do_CreateInstance("@mozilla.org/cycle-collector-logger;1"); + + if (aDumpAllTraces) { + nsCOMPtr<nsICycleCollectorListener> allTracesLogger; + logger->AllTraces(getter_AddRefs(allTracesLogger)); + logger = allTracesLogger; + } + + nsCOMPtr<nsICycleCollectorLogSink> logSink; + logger->GetLogSink(getter_AddRefs(logSink)); + + logSink->SetFilenameIdentifier(identifier); + + nsJSContext::CycleCollectNow(logger); + + nsCOMPtr<nsIFile> gcLog, ccLog; + logSink->GetGcLog(getter_AddRefs(gcLog)); + logSink->GetCcLog(getter_AddRefs(ccLog)); + callbackHolder->OnDump(gcLog, ccLog, /* parent = */ true); + + return NS_OK; +} + +NS_IMETHODIMP +nsMemoryInfoDumper::DumpGCAndCCLogsToSink(bool aDumpAllTraces, + nsICycleCollectorLogSink* aSink) +{ + nsCOMPtr<nsICycleCollectorListener> logger = + do_CreateInstance("@mozilla.org/cycle-collector-logger;1"); + + if (aDumpAllTraces) { + nsCOMPtr<nsICycleCollectorListener> allTracesLogger; + logger->AllTraces(getter_AddRefs(allTracesLogger)); + logger = allTracesLogger; + } + + logger->SetLogSink(aSink); + + nsJSContext::CycleCollectNow(logger); + + return NS_OK; +} + +static void +MakeFilename(const char* aPrefix, const nsAString& aIdentifier, + int aPid, const char* aSuffix, nsACString& aResult) +{ + aResult = nsPrintfCString("%s-%s-%d.%s", + aPrefix, + NS_ConvertUTF16toUTF8(aIdentifier).get(), + aPid, aSuffix); +} + +// This class wraps GZFileWriter so it can be used with JSONWriter, overcoming +// the following two problems: +// - It provides a JSONWriterFunc::Write() that calls nsGZFileWriter::Write(). +// - It can be stored as a UniquePtr, whereas nsGZFileWriter is refcounted. +class GZWriterWrapper : public JSONWriteFunc +{ +public: + explicit GZWriterWrapper(nsGZFileWriter* aGZWriter) + : mGZWriter(aGZWriter) + {} + + void Write(const char* aStr) + { + // Ignore any failure because JSONWriteFunc doesn't have a mechanism for + // handling errors. + Unused << mGZWriter->Write(aStr); + } + + nsresult Finish() { return mGZWriter->Finish(); } + +private: + RefPtr<nsGZFileWriter> mGZWriter; +}; + +// We need two callbacks: one that handles reports, and one that is called at +// the end of reporting. Both the callbacks need access to the same JSONWriter, +// so we implement both of them in this one class. +class HandleReportAndFinishReportingCallbacks final + : public nsIHandleReportCallback, public nsIFinishReportingCallback +{ +public: + NS_DECL_ISUPPORTS + + HandleReportAndFinishReportingCallbacks(UniquePtr<JSONWriter> aWriter, + nsIFinishDumpingCallback* aFinishDumping, + nsISupports* aFinishDumpingData) + : mWriter(Move(aWriter)) + , mFinishDumping(aFinishDumping) + , mFinishDumpingData(aFinishDumpingData) + { + } + + // This is the callback for nsIHandleReportCallback. + NS_IMETHOD Callback(const nsACString& aProcess, const nsACString& aPath, + int32_t aKind, int32_t aUnits, int64_t aAmount, + const nsACString& aDescription, + nsISupports* aData) override + { + nsAutoCString process; + if (aProcess.IsEmpty()) { + // If the process is empty, the report originated with the process doing + // the dumping. In that case, generate the process identifier, which is + // of the form "$PROCESS_NAME (pid $PID)", or just "(pid $PID)" if we + // don't have a process name. If we're the main process, we let + // $PROCESS_NAME be "Main Process". + if (XRE_IsParentProcess()) { + // We're the main process. + process.AssignLiteral("Main Process"); + } else if (ContentChild* cc = ContentChild::GetSingleton()) { + // Try to get the process name from ContentChild. + cc->GetProcessName(process); + } + ContentChild::AppendProcessId(process); + + } else { + // Otherwise, the report originated with another process and already has a + // process name. Just use that. + process = aProcess; + } + + mWriter->StartObjectElement(); + { + mWriter->StringProperty("process", process.get()); + mWriter->StringProperty("path", PromiseFlatCString(aPath).get()); + mWriter->IntProperty("kind", aKind); + mWriter->IntProperty("units", aUnits); + mWriter->IntProperty("amount", aAmount); + mWriter->StringProperty("description", + PromiseFlatCString(aDescription).get()); + } + mWriter->EndObject(); + + return NS_OK; + } + + // This is the callback for nsIFinishReportingCallback. + NS_IMETHOD Callback(nsISupports* aData) override + { + mWriter->EndArray(); // end of "reports" array + mWriter->End(); + + // The call to Finish() deallocates the memory allocated by the first Write + // call. Because that memory was live while the memory reporters ran and + // was measured by them -- by "heap-allocated" if nothing else -- we want + // DMD to see it as well. So we deliberately don't call Finish() until + // after DMD finishes. + nsresult rv = static_cast<GZWriterWrapper*>(mWriter->WriteFunc())->Finish(); + NS_ENSURE_SUCCESS(rv, rv); + + if (!mFinishDumping) { + return NS_OK; + } + + return mFinishDumping->Callback(mFinishDumpingData); + } + +private: + ~HandleReportAndFinishReportingCallbacks() {} + + UniquePtr<JSONWriter> mWriter; + nsCOMPtr<nsIFinishDumpingCallback> mFinishDumping; + nsCOMPtr<nsISupports> mFinishDumpingData; +}; + +NS_IMPL_ISUPPORTS(HandleReportAndFinishReportingCallbacks, + nsIHandleReportCallback, nsIFinishReportingCallback) + +class TempDirFinishCallback final : public nsIFinishDumpingCallback +{ +public: + NS_DECL_ISUPPORTS + + TempDirFinishCallback(nsIFile* aReportsTmpFile, + const nsCString& aReportsFinalFilename) + : mReportsTmpFile(aReportsTmpFile) + , mReportsFilename(aReportsFinalFilename) + { + } + + NS_IMETHOD Callback(nsISupports* aData) override + { + // Rename the memory reports file, now that we're done writing all the + // files. Its final name is "memory-report<-identifier>-<pid>.json.gz". + + nsCOMPtr<nsIFile> reportsFinalFile; + nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, + getter_AddRefs(reportsFinalFile)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + #ifdef ANDROID + rv = reportsFinalFile->AppendNative(NS_LITERAL_CSTRING("memory-reports")); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + #endif + + rv = reportsFinalFile->AppendNative(mReportsFilename); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = reportsFinalFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsAutoString reportsFinalFilename; + rv = reportsFinalFile->GetLeafName(reportsFinalFilename); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = mReportsTmpFile->MoveTo(/* directory */ nullptr, + reportsFinalFilename); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Write a message to the console. + + nsCOMPtr<nsIConsoleService> cs = + do_GetService(NS_CONSOLESERVICE_CONTRACTID, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsString path; + mReportsTmpFile->GetPath(path); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsString msg = NS_LITERAL_STRING("nsIMemoryInfoDumper dumped reports to "); + msg.Append(path); + return cs->LogStringMessage(msg.get()); + } + +private: + ~TempDirFinishCallback() {} + + nsCOMPtr<nsIFile> mReportsTmpFile; + nsCString mReportsFilename; +}; + +NS_IMPL_ISUPPORTS(TempDirFinishCallback, nsIFinishDumpingCallback) + +static nsresult +DumpMemoryInfoToFile( + nsIFile* aReportsFile, + nsIFinishDumpingCallback* aFinishDumping, + nsISupports* aFinishDumpingData, + bool aAnonymize, + bool aMinimizeMemoryUsage, + nsAString& aDMDIdentifier) +{ + RefPtr<nsGZFileWriter> gzWriter = new nsGZFileWriter(); + nsresult rv = gzWriter->Init(aReportsFile); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + auto jsonWriter = + MakeUnique<JSONWriter>(MakeUnique<GZWriterWrapper>(gzWriter)); + + nsCOMPtr<nsIMemoryReporterManager> mgr = + do_GetService("@mozilla.org/memory-reporter-manager;1"); + + // This is the first write to the file, and it causes |aWriter| to allocate + // over 200 KiB of memory. + jsonWriter->Start(); + { + // Increment this number if the format changes. + jsonWriter->IntProperty("version", 1); + jsonWriter->BoolProperty("hasMozMallocUsableSize", + mgr->GetHasMozMallocUsableSize()); + jsonWriter->StartArrayProperty("reports"); + } + + RefPtr<HandleReportAndFinishReportingCallbacks> + handleReportAndFinishReporting = + new HandleReportAndFinishReportingCallbacks(Move(jsonWriter), + aFinishDumping, + aFinishDumpingData); + rv = mgr->GetReportsExtended(handleReportAndFinishReporting, nullptr, + handleReportAndFinishReporting, nullptr, + aAnonymize, + aMinimizeMemoryUsage, + aDMDIdentifier); + return rv; +} + +NS_IMETHODIMP +nsMemoryInfoDumper::DumpMemoryReportsToNamedFile( + const nsAString& aFilename, + nsIFinishDumpingCallback* aFinishDumping, + nsISupports* aFinishDumpingData, + bool aAnonymize) +{ + MOZ_ASSERT(!aFilename.IsEmpty()); + + // Create the file. + + nsCOMPtr<nsIFile> reportsFile; + nsresult rv = NS_NewLocalFile(aFilename, false, getter_AddRefs(reportsFile)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + reportsFile->InitWithPath(aFilename); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + bool exists; + rv = reportsFile->Exists(&exists); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (!exists) { + rv = reportsFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + nsString dmdIdent = EmptyString(); + return DumpMemoryInfoToFile(reportsFile, aFinishDumping, aFinishDumpingData, + aAnonymize, /* minimizeMemoryUsage = */ false, + dmdIdent); +} + +NS_IMETHODIMP +nsMemoryInfoDumper::DumpMemoryInfoToTempDir(const nsAString& aIdentifier, + bool aAnonymize, + bool aMinimizeMemoryUsage) +{ + nsString identifier(aIdentifier); + EnsureNonEmptyIdentifier(identifier); + + // Open a new file named something like + // + // incomplete-memory-report-<identifier>-<pid>.json.gz + // + // in NS_OS_TEMP_DIR for writing. When we're finished writing the report, + // we'll rename this file and get rid of the "incomplete-" prefix. + // + // We do this because we don't want scripts which poll the filesystem + // looking for memory report dumps to grab a file before we're finished + // writing to it. + + // The "unified" indicates that we merge the memory reports from all + // processes and write out one file, rather than a separate file for + // each process as was the case before bug 946407. This is so that + // the get_about_memory.py script in the B2G repository can + // determine when it's done waiting for files to appear. + nsCString reportsFinalFilename; + MakeFilename("unified-memory-report", identifier, getpid(), "json.gz", + reportsFinalFilename); + + nsCOMPtr<nsIFile> reportsTmpFile; + nsresult rv; + // In Android case, this function will open a file named aFilename under + // specific folder (/data/local/tmp/memory-reports). Otherwise, it will + // open a file named aFilename under "NS_OS_TEMP_DIR". + rv = nsDumpUtils::OpenTempFile(NS_LITERAL_CSTRING("incomplete-") + + reportsFinalFilename, + getter_AddRefs(reportsTmpFile), + NS_LITERAL_CSTRING("memory-reports")); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + RefPtr<TempDirFinishCallback> finishDumping = + new TempDirFinishCallback(reportsTmpFile, reportsFinalFilename); + + return DumpMemoryInfoToFile(reportsTmpFile, finishDumping, nullptr, + aAnonymize, aMinimizeMemoryUsage, identifier); +} + +#ifdef MOZ_DMD +dmd::DMDFuncs::Singleton dmd::DMDFuncs::sSingleton; + +nsresult +nsMemoryInfoDumper::OpenDMDFile(const nsAString& aIdentifier, int aPid, + FILE** aOutFile) +{ + if (!dmd::IsRunning()) { + *aOutFile = nullptr; + return NS_OK; + } + + // Create a filename like dmd-<identifier>-<pid>.json.gz, which will be used + // if DMD is enabled. + nsCString dmdFilename; + MakeFilename("dmd", aIdentifier, aPid, "json.gz", dmdFilename); + + // Open a new DMD file named |dmdFilename| in NS_OS_TEMP_DIR for writing, + // and dump DMD output to it. This must occur after the memory reporters + // have been run (above), but before the memory-reports file has been + // renamed (so scripts can detect the DMD file, if present). + + nsresult rv; + nsCOMPtr<nsIFile> dmdFile; + rv = nsDumpUtils::OpenTempFile(dmdFilename, + getter_AddRefs(dmdFile), + NS_LITERAL_CSTRING("memory-reports")); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + rv = dmdFile->OpenANSIFileDesc("wb", aOutFile); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "OpenANSIFileDesc failed"); + + // Print the path, because on some platforms (e.g. Mac) it's not obvious. + nsCString path; + rv = dmdFile->GetNativePath(path); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + dmd::StatusMsg("opened %s for writing\n", path.get()); + + return rv; +} + +nsresult +nsMemoryInfoDumper::DumpDMDToFile(FILE* aFile) +{ + RefPtr<nsGZFileWriter> gzWriter = new nsGZFileWriter(); + nsresult rv = gzWriter->InitANSIFileDesc(aFile); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Dump DMD's memory reports analysis to the file. + dmd::Analyze(MakeUnique<GZWriterWrapper>(gzWriter)); + + rv = gzWriter->Finish(); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Finish failed"); + return rv; +} +#endif // MOZ_DMD + diff --git a/xpcom/base/nsMemoryInfoDumper.h b/xpcom/base/nsMemoryInfoDumper.h new file mode 100644 index 000000000..6bba176f2 --- /dev/null +++ b/xpcom/base/nsMemoryInfoDumper.h @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_nsMemoryInfoDumper_h +#define mozilla_nsMemoryInfoDumper_h + +#include "nsIMemoryInfoDumper.h" +#include <stdio.h> + +/** + * This class facilitates dumping information about our memory usage to disk. + * + * Its cpp file also has Linux-only code which watches various OS signals and + * dumps memory info upon receiving a signal. You can activate these listeners + * by calling Initialize(). + */ +class nsMemoryInfoDumper : public nsIMemoryInfoDumper +{ + virtual ~nsMemoryInfoDumper(); + +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIMEMORYINFODUMPER + + nsMemoryInfoDumper(); + + static void Initialize(); + +#ifdef MOZ_DMD + // Open an appropriately named file for a DMD report. If DMD is + // disabled, return a null FILE* instead. + static nsresult OpenDMDFile(const nsAString& aIdentifier, int aPid, + FILE** aOutFile); + // Write a DMD report to the given file and close it. + static nsresult DumpDMDToFile(FILE* aFile); +#endif +}; + +#define NS_MEMORY_INFO_DUMPER_CID \ +{ 0x00bd71fb, 0x7f09, 0x4ec3, \ +{ 0x96, 0xaf, 0xa0, 0xb5, 0x22, 0xb7, 0x79, 0x69 } } + +#endif diff --git a/xpcom/base/nsMemoryReporterManager.cpp b/xpcom/base/nsMemoryReporterManager.cpp new file mode 100644 index 000000000..aa3d74dfd --- /dev/null +++ b/xpcom/base/nsMemoryReporterManager.cpp @@ -0,0 +1,2717 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsAtomTable.h" +#include "nsAutoPtr.h" +#include "nsCOMPtr.h" +#include "nsCOMArray.h" +#include "nsPrintfCString.h" +#include "nsServiceManagerUtils.h" +#include "nsMemoryReporterManager.h" +#include "nsITimer.h" +#include "nsThreadUtils.h" +#include "nsPIDOMWindow.h" +#include "nsIObserverService.h" +#include "nsIGlobalObject.h" +#include "nsIXPConnect.h" +#if defined(XP_UNIX) || defined(MOZ_DMD) +#include "nsMemoryInfoDumper.h" +#endif +#include "mozilla/Attributes.h" +#include "mozilla/PodOperations.h" +#include "mozilla/Preferences.h" +#include "mozilla/Services.h" +#include "mozilla/Telemetry.h" +#include "mozilla/UniquePtrExtensions.h" +#include "mozilla/dom/PMemoryReportRequestParent.h" // for dom::MemoryReport +#include "mozilla/dom/ContentParent.h" +#include "mozilla/ipc/FileDescriptorUtils.h" + +#ifdef XP_WIN +#include <process.h> +#ifndef getpid +#define getpid _getpid +#endif +#else +#include <unistd.h> +#endif + +using namespace mozilla; + +#if defined(MOZ_MEMORY) +# define HAVE_JEMALLOC_STATS 1 +# include "mozmemory.h" +#endif // MOZ_MEMORY + +#if defined(XP_LINUX) + +#include <malloc.h> +#include <string.h> +#include <stdlib.h> + +static MOZ_MUST_USE nsresult +GetProcSelfStatmField(int aField, int64_t* aN) +{ + // There are more than two fields, but we're only interested in the first + // two. + static const int MAX_FIELD = 2; + size_t fields[MAX_FIELD]; + MOZ_ASSERT(aField < MAX_FIELD, "bad field number"); + FILE* f = fopen("/proc/self/statm", "r"); + if (f) { + int nread = fscanf(f, "%zu %zu", &fields[0], &fields[1]); + fclose(f); + if (nread == MAX_FIELD) { + *aN = fields[aField] * getpagesize(); + return NS_OK; + } + } + return NS_ERROR_FAILURE; +} + +static MOZ_MUST_USE nsresult +GetProcSelfSmapsPrivate(int64_t* aN) +{ + // You might be tempted to calculate USS by subtracting the "shared" value + // from the "resident" value in /proc/<pid>/statm. But at least on Linux, + // statm's "shared" value actually counts pages backed by files, which has + // little to do with whether the pages are actually shared. /proc/self/smaps + // on the other hand appears to give us the correct information. + + FILE* f = fopen("/proc/self/smaps", "r"); + if (NS_WARN_IF(!f)) { + return NS_ERROR_UNEXPECTED; + } + + // We carry over the end of the buffer to the beginning to make sure we only + // interpret complete lines. + static const uint32_t carryOver = 32; + static const uint32_t readSize = 4096; + + int64_t amount = 0; + char buffer[carryOver + readSize + 1]; + + // Fill the beginning of the buffer with spaces, as a sentinel for the first + // iteration. + memset(buffer, ' ', carryOver); + + for (;;) { + size_t bytes = fread(buffer + carryOver, sizeof(*buffer), readSize, f); + char* end = buffer + bytes; + char* ptr = buffer; + end[carryOver] = '\0'; + // We are looking for lines like "Private_{Clean,Dirty}: 4 kB". + while ((ptr = strstr(ptr, "Private"))) { + if (ptr >= end) { + break; + } + ptr += sizeof("Private_Xxxxx:"); + amount += strtol(ptr, nullptr, 10); + } + if (bytes < readSize) { + // We do not expect any match within the end of the buffer. + MOZ_ASSERT(!strstr(end, "Private")); + break; + } + // Carry the end of the buffer over to the beginning. + memcpy(buffer, end, carryOver); + } + + fclose(f); + // Convert from kB to bytes. + *aN = amount * 1024; + return NS_OK; +} + +#define HAVE_VSIZE_AND_RESIDENT_REPORTERS 1 +static MOZ_MUST_USE nsresult +VsizeDistinguishedAmount(int64_t* aN) +{ + return GetProcSelfStatmField(0, aN); +} + +static MOZ_MUST_USE nsresult +ResidentDistinguishedAmount(int64_t* aN) +{ + return GetProcSelfStatmField(1, aN); +} + +static MOZ_MUST_USE nsresult +ResidentFastDistinguishedAmount(int64_t* aN) +{ + return ResidentDistinguishedAmount(aN); +} + +#define HAVE_RESIDENT_UNIQUE_REPORTER 1 +static MOZ_MUST_USE nsresult +ResidentUniqueDistinguishedAmount(int64_t* aN) +{ + return GetProcSelfSmapsPrivate(aN); +} + +#ifdef HAVE_MALLINFO +#define HAVE_SYSTEM_HEAP_REPORTER 1 +static MOZ_MUST_USE nsresult +SystemHeapSize(int64_t* aSizeOut) +{ + struct mallinfo info = mallinfo(); + + // The documentation in the glibc man page makes it sound like |uordblks| + // would suffice, but that only gets the small allocations that are put in + // the brk heap. We need |hblkhd| as well to get the larger allocations + // that are mmapped. + // + // The fields in |struct mallinfo| are all |int|, <sigh>, so it is + // unreliable if memory usage gets high. However, the system heap size on + // Linux should usually be zero (so long as jemalloc is enabled) so that + // shouldn't be a problem. Nonetheless, cast the |int|s to |size_t| before + // adding them to provide a small amount of extra overflow protection. + *aSizeOut = size_t(info.hblkhd) + size_t(info.uordblks); + return NS_OK; +} +#endif + +#elif defined(__DragonFly__) || defined(__FreeBSD__) \ + || defined(__NetBSD__) || defined(__OpenBSD__) \ + || defined(__FreeBSD_kernel__) + +#include <sys/param.h> +#include <sys/sysctl.h> +#if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) +#include <sys/user.h> +#endif + +#include <unistd.h> + +#if defined(__NetBSD__) +#undef KERN_PROC +#define KERN_PROC KERN_PROC2 +#define KINFO_PROC struct kinfo_proc2 +#else +#define KINFO_PROC struct kinfo_proc +#endif + +#if defined(__DragonFly__) +#define KP_SIZE(kp) (kp.kp_vm_map_size) +#define KP_RSS(kp) (kp.kp_vm_rssize * getpagesize()) +#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) +#define KP_SIZE(kp) (kp.ki_size) +#define KP_RSS(kp) (kp.ki_rssize * getpagesize()) +#elif defined(__NetBSD__) +#define KP_SIZE(kp) (kp.p_vm_msize * getpagesize()) +#define KP_RSS(kp) (kp.p_vm_rssize * getpagesize()) +#elif defined(__OpenBSD__) +#define KP_SIZE(kp) ((kp.p_vm_dsize + kp.p_vm_ssize \ + + kp.p_vm_tsize) * getpagesize()) +#define KP_RSS(kp) (kp.p_vm_rssize * getpagesize()) +#endif + +static MOZ_MUST_USE nsresult +GetKinfoProcSelf(KINFO_PROC* aProc) +{ + int mib[] = { + CTL_KERN, + KERN_PROC, + KERN_PROC_PID, + getpid(), +#if defined(__NetBSD__) || defined(__OpenBSD__) + sizeof(KINFO_PROC), + 1, +#endif + }; + u_int miblen = sizeof(mib) / sizeof(mib[0]); + size_t size = sizeof(KINFO_PROC); + if (sysctl(mib, miblen, aProc, &size, nullptr, 0)) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +#define HAVE_VSIZE_AND_RESIDENT_REPORTERS 1 +static MOZ_MUST_USE nsresult +VsizeDistinguishedAmount(int64_t* aN) +{ + KINFO_PROC proc; + nsresult rv = GetKinfoProcSelf(&proc); + if (NS_SUCCEEDED(rv)) { + *aN = KP_SIZE(proc); + } + return rv; +} + +static MOZ_MUST_USE nsresult +ResidentDistinguishedAmount(int64_t* aN) +{ + KINFO_PROC proc; + nsresult rv = GetKinfoProcSelf(&proc); + if (NS_SUCCEEDED(rv)) { + *aN = KP_RSS(proc); + } + return rv; +} + +static MOZ_MUST_USE nsresult +ResidentFastDistinguishedAmount(int64_t* aN) +{ + return ResidentDistinguishedAmount(aN); +} + +#ifdef __FreeBSD__ +#include <libutil.h> +#include <algorithm> + +static MOZ_MUST_USE nsresult +GetKinfoVmentrySelf(int64_t* aPrss, uint64_t* aMaxreg) +{ + int cnt; + struct kinfo_vmentry* vmmap; + struct kinfo_vmentry* kve; + if (!(vmmap = kinfo_getvmmap(getpid(), &cnt))) { + return NS_ERROR_FAILURE; + } + if (aPrss) { + *aPrss = 0; + } + if (aMaxreg) { + *aMaxreg = 0; + } + + for (int i = 0; i < cnt; i++) { + kve = &vmmap[i]; + if (aPrss) { + *aPrss += kve->kve_private_resident; + } + if (aMaxreg) { + *aMaxreg = std::max(*aMaxreg, kve->kve_end - kve->kve_start); + } + } + + free(vmmap); + return NS_OK; +} + +#define HAVE_PRIVATE_REPORTER 1 +static MOZ_MUST_USE nsresult +PrivateDistinguishedAmount(int64_t* aN) +{ + int64_t priv; + nsresult rv = GetKinfoVmentrySelf(&priv, nullptr); + NS_ENSURE_SUCCESS(rv, rv); + *aN = priv * getpagesize(); + return NS_OK; +} + +#define HAVE_VSIZE_MAX_CONTIGUOUS_REPORTER 1 +static MOZ_MUST_USE nsresult +VsizeMaxContiguousDistinguishedAmount(int64_t* aN) +{ + uint64_t biggestRegion; + nsresult rv = GetKinfoVmentrySelf(nullptr, &biggestRegion); + if (NS_SUCCEEDED(rv)) { + *aN = biggestRegion; + } + return NS_OK; +} +#endif // FreeBSD + +#elif defined(SOLARIS) + +#include <procfs.h> +#include <fcntl.h> +#include <unistd.h> + +static void +XMappingIter(int64_t& aVsize, int64_t& aResident) +{ + aVsize = -1; + aResident = -1; + int mapfd = open("/proc/self/xmap", O_RDONLY); + struct stat st; + prxmap_t* prmapp = nullptr; + if (mapfd >= 0) { + if (!fstat(mapfd, &st)) { + int nmap = st.st_size / sizeof(prxmap_t); + while (1) { + // stat(2) on /proc/<pid>/xmap returns an incorrect value, + // prior to the release of Solaris 11. + // Here is a workaround for it. + nmap *= 2; + prmapp = (prxmap_t*)malloc((nmap + 1) * sizeof(prxmap_t)); + if (!prmapp) { + // out of memory + break; + } + int n = pread(mapfd, prmapp, (nmap + 1) * sizeof(prxmap_t), 0); + if (n < 0) { + break; + } + if (nmap >= n / sizeof(prxmap_t)) { + aVsize = 0; + aResident = 0; + for (int i = 0; i < n / sizeof(prxmap_t); i++) { + aVsize += prmapp[i].pr_size; + aResident += prmapp[i].pr_rss * prmapp[i].pr_pagesize; + } + break; + } + free(prmapp); + } + free(prmapp); + } + close(mapfd); + } +} + +#define HAVE_VSIZE_AND_RESIDENT_REPORTERS 1 +static MOZ_MUST_USE nsresult +VsizeDistinguishedAmount(int64_t* aN) +{ + int64_t vsize, resident; + XMappingIter(vsize, resident); + if (vsize == -1) { + return NS_ERROR_FAILURE; + } + *aN = vsize; + return NS_OK; +} + +static MOZ_MUST_USE nsresult +ResidentDistinguishedAmount(int64_t* aN) +{ + int64_t vsize, resident; + XMappingIter(vsize, resident); + if (resident == -1) { + return NS_ERROR_FAILURE; + } + *aN = resident; + return NS_OK; +} + +static MOZ_MUST_USE nsresult +ResidentFastDistinguishedAmount(int64_t* aN) +{ + return ResidentDistinguishedAmount(aN); +} + +#elif defined(XP_MACOSX) + +#include <mach/mach_init.h> +#include <mach/mach_vm.h> +#include <mach/shared_region.h> +#include <mach/task.h> +#include <sys/sysctl.h> + +static MOZ_MUST_USE bool +GetTaskBasicInfo(struct task_basic_info* aTi) +{ + mach_msg_type_number_t count = TASK_BASIC_INFO_COUNT; + kern_return_t kr = task_info(mach_task_self(), TASK_BASIC_INFO, + (task_info_t)aTi, &count); + return kr == KERN_SUCCESS; +} + +// The VSIZE figure on Mac includes huge amounts of shared memory and is always +// absurdly high, eg. 2GB+ even at start-up. But both 'top' and 'ps' report +// it, so we might as well too. +#define HAVE_VSIZE_AND_RESIDENT_REPORTERS 1 +static MOZ_MUST_USE nsresult +VsizeDistinguishedAmount(int64_t* aN) +{ + task_basic_info ti; + if (!GetTaskBasicInfo(&ti)) { + return NS_ERROR_FAILURE; + } + *aN = ti.virtual_size; + return NS_OK; +} + +// If we're using jemalloc on Mac, we need to instruct jemalloc to purge the +// pages it has madvise(MADV_FREE)'d before we read our RSS in order to get +// an accurate result. The OS will take away MADV_FREE'd pages when there's +// memory pressure, so ideally, they shouldn't count against our RSS. +// +// Purging these pages can take a long time for some users (see bug 789975), +// so we provide the option to get the RSS without purging first. +static MOZ_MUST_USE nsresult +ResidentDistinguishedAmountHelper(int64_t* aN, bool aDoPurge) +{ +#ifdef HAVE_JEMALLOC_STATS +#ifndef MOZ_JEMALLOC4 + if (aDoPurge) { + Telemetry::AutoTimer<Telemetry::MEMORY_FREE_PURGED_PAGES_MS> timer; + jemalloc_purge_freed_pages(); + } +#endif +#endif + + task_basic_info ti; + if (!GetTaskBasicInfo(&ti)) { + return NS_ERROR_FAILURE; + } + *aN = ti.resident_size; + return NS_OK; +} + +static MOZ_MUST_USE nsresult +ResidentFastDistinguishedAmount(int64_t* aN) +{ + return ResidentDistinguishedAmountHelper(aN, /* doPurge = */ false); +} + +static MOZ_MUST_USE nsresult +ResidentDistinguishedAmount(int64_t* aN) +{ + return ResidentDistinguishedAmountHelper(aN, /* doPurge = */ true); +} + +#define HAVE_RESIDENT_UNIQUE_REPORTER 1 + +static bool +InSharedRegion(mach_vm_address_t aAddr, cpu_type_t aType) +{ + mach_vm_address_t base; + mach_vm_address_t size; + + switch (aType) { + case CPU_TYPE_ARM: + base = SHARED_REGION_BASE_ARM; + size = SHARED_REGION_SIZE_ARM; + break; + case CPU_TYPE_I386: + base = SHARED_REGION_BASE_I386; + size = SHARED_REGION_SIZE_I386; + break; + case CPU_TYPE_X86_64: + base = SHARED_REGION_BASE_X86_64; + size = SHARED_REGION_SIZE_X86_64; + break; + default: + return false; + } + + return base <= aAddr && aAddr < (base + size); +} + +static MOZ_MUST_USE nsresult +ResidentUniqueDistinguishedAmount(int64_t* aN) +{ + if (!aN) { + return NS_ERROR_FAILURE; + } + + cpu_type_t cpu_type; + size_t len = sizeof(cpu_type); + if (sysctlbyname("sysctl.proc_cputype", &cpu_type, &len, NULL, 0) != 0) { + return NS_ERROR_FAILURE; + } + + // Roughly based on libtop_update_vm_regions in + // http://www.opensource.apple.com/source/top/top-100.1.2/libtop.c + size_t privatePages = 0; + mach_vm_size_t size = 0; + for (mach_vm_address_t addr = MACH_VM_MIN_ADDRESS; ; addr += size) { + vm_region_top_info_data_t info; + mach_msg_type_number_t infoCount = VM_REGION_TOP_INFO_COUNT; + mach_port_t objectName; + + kern_return_t kr = + mach_vm_region(mach_task_self(), &addr, &size, VM_REGION_TOP_INFO, + reinterpret_cast<vm_region_info_t>(&info), + &infoCount, &objectName); + if (kr == KERN_INVALID_ADDRESS) { + // Done iterating VM regions. + break; + } else if (kr != KERN_SUCCESS) { + return NS_ERROR_FAILURE; + } + + if (InSharedRegion(addr, cpu_type) && info.share_mode != SM_PRIVATE) { + continue; + } + + switch (info.share_mode) { + case SM_LARGE_PAGE: + // NB: Large pages are not shareable and always resident. + case SM_PRIVATE: + privatePages += info.private_pages_resident; + privatePages += info.shared_pages_resident; + break; + case SM_COW: + privatePages += info.private_pages_resident; + if (info.ref_count == 1) { + // Treat copy-on-write pages as private if they only have one reference. + privatePages += info.shared_pages_resident; + } + break; + case SM_SHARED: + default: + break; + } + } + + vm_size_t pageSize; + if (host_page_size(mach_host_self(), &pageSize) != KERN_SUCCESS) { + pageSize = PAGE_SIZE; + } + + *aN = privatePages * pageSize; + return NS_OK; +} + +#elif defined(XP_WIN) + +#include <windows.h> +#include <psapi.h> +#include <algorithm> + +#define HAVE_VSIZE_AND_RESIDENT_REPORTERS 1 +static MOZ_MUST_USE nsresult +VsizeDistinguishedAmount(int64_t* aN) +{ + MEMORYSTATUSEX s; + s.dwLength = sizeof(s); + + if (!GlobalMemoryStatusEx(&s)) { + return NS_ERROR_FAILURE; + } + + *aN = s.ullTotalVirtual - s.ullAvailVirtual; + return NS_OK; +} + +static MOZ_MUST_USE nsresult +ResidentDistinguishedAmount(int64_t* aN) +{ + PROCESS_MEMORY_COUNTERS pmc; + pmc.cb = sizeof(PROCESS_MEMORY_COUNTERS); + + if (!GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc))) { + return NS_ERROR_FAILURE; + } + + *aN = pmc.WorkingSetSize; + return NS_OK; +} + +static MOZ_MUST_USE nsresult +ResidentFastDistinguishedAmount(int64_t* aN) +{ + return ResidentDistinguishedAmount(aN); +} + +#define HAVE_RESIDENT_UNIQUE_REPORTER 1 + +static MOZ_MUST_USE nsresult +ResidentUniqueDistinguishedAmount(int64_t* aN) +{ + // Determine how many entries we need. + PSAPI_WORKING_SET_INFORMATION tmp; + DWORD tmpSize = sizeof(tmp); + memset(&tmp, 0, tmpSize); + + HANDLE proc = GetCurrentProcess(); + QueryWorkingSet(proc, &tmp, tmpSize); + + // Fudge the size in case new entries are added between calls. + size_t entries = tmp.NumberOfEntries * 2; + + if (!entries) { + return NS_ERROR_FAILURE; + } + + DWORD infoArraySize = tmpSize + (entries * sizeof(PSAPI_WORKING_SET_BLOCK)); + UniqueFreePtr<PSAPI_WORKING_SET_INFORMATION> infoArray( + static_cast<PSAPI_WORKING_SET_INFORMATION*>(malloc(infoArraySize))); + + if (!infoArray) { + return NS_ERROR_FAILURE; + } + + if (!QueryWorkingSet(proc, infoArray.get(), infoArraySize)) { + return NS_ERROR_FAILURE; + } + + entries = static_cast<size_t>(infoArray->NumberOfEntries); + size_t privatePages = 0; + for (size_t i = 0; i < entries; i++) { + // Count shared pages that only one process is using as private. + if (!infoArray->WorkingSetInfo[i].Shared || + infoArray->WorkingSetInfo[i].ShareCount <= 1) { + privatePages++; + } + } + + SYSTEM_INFO si; + GetSystemInfo(&si); + + *aN = privatePages * si.dwPageSize; + return NS_OK; +} + +#define HAVE_VSIZE_MAX_CONTIGUOUS_REPORTER 1 +static MOZ_MUST_USE nsresult +VsizeMaxContiguousDistinguishedAmount(int64_t* aN) +{ + SIZE_T biggestRegion = 0; + MEMORY_BASIC_INFORMATION vmemInfo = { 0 }; + for (size_t currentAddress = 0; ; ) { + if (!VirtualQuery((LPCVOID)currentAddress, &vmemInfo, sizeof(vmemInfo))) { + // Something went wrong, just return whatever we've got already. + break; + } + + if (vmemInfo.State == MEM_FREE) { + biggestRegion = std::max(biggestRegion, vmemInfo.RegionSize); + } + + SIZE_T lastAddress = currentAddress; + currentAddress += vmemInfo.RegionSize; + + // If we overflow, we've examined all of the address space. + if (currentAddress < lastAddress) { + break; + } + } + + *aN = biggestRegion; + return NS_OK; +} + +#define HAVE_PRIVATE_REPORTER 1 +static MOZ_MUST_USE nsresult +PrivateDistinguishedAmount(int64_t* aN) +{ + PROCESS_MEMORY_COUNTERS_EX pmcex; + pmcex.cb = sizeof(PROCESS_MEMORY_COUNTERS_EX); + + if (!GetProcessMemoryInfo(GetCurrentProcess(), + (PPROCESS_MEMORY_COUNTERS) &pmcex, sizeof(pmcex))) { + return NS_ERROR_FAILURE; + } + + *aN = pmcex.PrivateUsage; + return NS_OK; +} + +#define HAVE_SYSTEM_HEAP_REPORTER 1 +// Windows can have multiple separate heaps. During testing there were multiple +// heaps present but the non-default ones had sizes no more than a few 10s of +// KiBs. So we combine their sizes into a single measurement. +static MOZ_MUST_USE nsresult +SystemHeapSize(int64_t* aSizeOut) +{ + // Get the number of heaps. + DWORD nHeaps = GetProcessHeaps(0, nullptr); + NS_ENSURE_TRUE(nHeaps != 0, NS_ERROR_FAILURE); + + // Get handles to all heaps, checking that the number of heaps hasn't + // changed in the meantime. + UniquePtr<HANDLE[]> heaps(new HANDLE[nHeaps]); + DWORD nHeaps2 = GetProcessHeaps(nHeaps, heaps.get()); + NS_ENSURE_TRUE(nHeaps2 != 0 && nHeaps2 == nHeaps, NS_ERROR_FAILURE); + + // Lock and iterate over each heap to get its size. + int64_t heapsSize = 0; + for (DWORD i = 0; i < nHeaps; i++) { + HANDLE heap = heaps[i]; + + NS_ENSURE_TRUE(HeapLock(heap), NS_ERROR_FAILURE); + + int64_t heapSize = 0; + PROCESS_HEAP_ENTRY entry; + entry.lpData = nullptr; + while (HeapWalk(heap, &entry)) { + // We don't count entry.cbOverhead, because we just want to measure the + // space available to the program. + if (entry.wFlags & PROCESS_HEAP_ENTRY_BUSY) { + heapSize += entry.cbData; + } + } + + // Check this result only after unlocking the heap, so that we don't leave + // the heap locked if there was an error. + DWORD lastError = GetLastError(); + + // I have no idea how things would proceed if unlocking this heap failed... + NS_ENSURE_TRUE(HeapUnlock(heap), NS_ERROR_FAILURE); + + NS_ENSURE_TRUE(lastError == ERROR_NO_MORE_ITEMS, NS_ERROR_FAILURE); + + heapsSize += heapSize; + } + + *aSizeOut = heapsSize; + return NS_OK; +} + +struct SegmentKind +{ + DWORD mState; + DWORD mType; + DWORD mProtect; + int mIsStack; +}; + +struct SegmentEntry : public PLDHashEntryHdr +{ + static PLDHashNumber HashKey(const void* aKey) + { + auto kind = static_cast<const SegmentKind*>(aKey); + return mozilla::HashGeneric(kind->mState, kind->mType, kind->mProtect, + kind->mIsStack); + } + + static bool MatchEntry(const PLDHashEntryHdr* aEntry, const void* aKey) + { + auto kind = static_cast<const SegmentKind*>(aKey); + auto entry = static_cast<const SegmentEntry*>(aEntry); + return kind->mState == entry->mKind.mState && + kind->mType == entry->mKind.mType && + kind->mProtect == entry->mKind.mProtect && + kind->mIsStack == entry->mKind.mIsStack; + } + + static void InitEntry(PLDHashEntryHdr* aEntry, const void* aKey) + { + auto kind = static_cast<const SegmentKind*>(aKey); + auto entry = static_cast<SegmentEntry*>(aEntry); + entry->mKind = *kind; + entry->mCount = 0; + entry->mSize = 0; + } + + static const PLDHashTableOps Ops; + + SegmentKind mKind; // The segment kind. + uint32_t mCount; // The number of segments of this kind. + size_t mSize; // The combined size of segments of this kind. +}; + +/* static */ const PLDHashTableOps SegmentEntry::Ops = { + SegmentEntry::HashKey, + SegmentEntry::MatchEntry, + PLDHashTable::MoveEntryStub, + PLDHashTable::ClearEntryStub, + SegmentEntry::InitEntry +}; + +class WindowsAddressSpaceReporter final : public nsIMemoryReporter +{ + ~WindowsAddressSpaceReporter() {} + +public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override + { + // First iterate over all the segments and record how many of each kind + // there were and their aggregate sizes. We use a hash table for this + // because there are a couple of dozen different kinds possible. + + PLDHashTable table(&SegmentEntry::Ops, sizeof(SegmentEntry)); + MEMORY_BASIC_INFORMATION info = { 0 }; + bool isPrevSegStackGuard = false; + for (size_t currentAddress = 0; ; ) { + if (!VirtualQuery((LPCVOID)currentAddress, &info, sizeof(info))) { + // Something went wrong, just return whatever we've got already. + break; + } + + size_t size = info.RegionSize; + + // Note that |type| and |protect| are ignored in some cases. + DWORD state = info.State; + DWORD type = + (state == MEM_RESERVE || state == MEM_COMMIT) ? info.Type : 0; + DWORD protect = (state == MEM_COMMIT) ? info.Protect : 0; + bool isStack = isPrevSegStackGuard && + state == MEM_COMMIT && + type == MEM_PRIVATE && + protect == PAGE_READWRITE; + + SegmentKind kind = { state, type, protect, isStack ? 1 : 0 }; + auto entry = + static_cast<SegmentEntry*>(table.Add(&kind, mozilla::fallible)); + if (entry) { + entry->mCount += 1; + entry->mSize += size; + } + + isPrevSegStackGuard = info.State == MEM_COMMIT && + info.Type == MEM_PRIVATE && + info.Protect == (PAGE_READWRITE|PAGE_GUARD); + + size_t lastAddress = currentAddress; + currentAddress += size; + + // If we overflow, we've examined all of the address space. + if (currentAddress < lastAddress) { + break; + } + } + + // Then iterate over the hash table and report the details for each segment + // kind. + + for (auto iter = table.Iter(); !iter.Done(); iter.Next()) { + // For each range of pages, we consider one or more of its State, Type + // and Protect values. These are documented at + // https://msdn.microsoft.com/en-us/library/windows/desktop/aa366775%28v=vs.85%29.aspx + // (for State and Type) and + // https://msdn.microsoft.com/en-us/library/windows/desktop/aa366786%28v=vs.85%29.aspx + // (for Protect). + // + // Not all State values have accompanying Type and Protection values. + bool doType = false; + bool doProtect = false; + + auto entry = static_cast<const SegmentEntry*>(iter.Get()); + + nsCString path("address-space"); + + switch (entry->mKind.mState) { + case MEM_FREE: + path.AppendLiteral("/free"); + break; + + case MEM_RESERVE: + path.AppendLiteral("/reserved"); + doType = true; + break; + + case MEM_COMMIT: + path.AppendLiteral("/commit"); + doType = true; + doProtect = true; + break; + + default: + // Should be impossible, but handle it just in case. + path.AppendLiteral("/???"); + break; + } + + if (doType) { + switch (entry->mKind.mType) { + case MEM_IMAGE: + path.AppendLiteral("/image"); + break; + + case MEM_MAPPED: + path.AppendLiteral("/mapped"); + break; + + case MEM_PRIVATE: + path.AppendLiteral("/private"); + break; + + default: + // Should be impossible, but handle it just in case. + path.AppendLiteral("/???"); + break; + } + } + + if (doProtect) { + DWORD protect = entry->mKind.mProtect; + // Basic attributes. Exactly one of these should be set. + if (protect & PAGE_EXECUTE) { + path.AppendLiteral("/execute"); + } + if (protect & PAGE_EXECUTE_READ) { + path.AppendLiteral("/execute-read"); + } + if (protect & PAGE_EXECUTE_READWRITE) { + path.AppendLiteral("/execute-readwrite"); + } + if (protect & PAGE_EXECUTE_WRITECOPY) { + path.AppendLiteral("/execute-writecopy"); + } + if (protect & PAGE_NOACCESS) { + path.AppendLiteral("/noaccess"); + } + if (protect & PAGE_READONLY) { + path.AppendLiteral("/readonly"); + } + if (protect & PAGE_READWRITE) { + path.AppendLiteral("/readwrite"); + } + if (protect & PAGE_WRITECOPY) { + path.AppendLiteral("/writecopy"); + } + + // Modifiers. At most one of these should be set. + if (protect & PAGE_GUARD) { + path.AppendLiteral("+guard"); + } + if (protect & PAGE_NOCACHE) { + path.AppendLiteral("+nocache"); + } + if (protect & PAGE_WRITECOMBINE) { + path.AppendLiteral("+writecombine"); + } + + // Annotate likely stack segments, too. + if (entry->mKind.mIsStack) { + path.AppendLiteral("+stack"); + } + } + + // Append the segment count. + path.AppendPrintf("(segments=%u)", entry->mCount); + + aHandleReport->Callback( + EmptyCString(), path, KIND_OTHER, UNITS_BYTES, entry->mSize, + NS_LITERAL_CSTRING("From MEMORY_BASIC_INFORMATION."), aData); + } + + return NS_OK; + } +}; +NS_IMPL_ISUPPORTS(WindowsAddressSpaceReporter, nsIMemoryReporter) + +#endif // XP_<PLATFORM> + +#ifdef HAVE_VSIZE_MAX_CONTIGUOUS_REPORTER +class VsizeMaxContiguousReporter final : public nsIMemoryReporter +{ + ~VsizeMaxContiguousReporter() {} + +public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override + { + int64_t amount; + if (NS_SUCCEEDED(VsizeMaxContiguousDistinguishedAmount(&amount))) { + MOZ_COLLECT_REPORT( + "vsize-max-contiguous", KIND_OTHER, UNITS_BYTES, amount, + "Size of the maximum contiguous block of available virtual memory."); + } + return NS_OK; + } +}; +NS_IMPL_ISUPPORTS(VsizeMaxContiguousReporter, nsIMemoryReporter) +#endif + +#ifdef HAVE_PRIVATE_REPORTER +class PrivateReporter final : public nsIMemoryReporter +{ + ~PrivateReporter() {} + +public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override + { + int64_t amount; + if (NS_SUCCEEDED(PrivateDistinguishedAmount(&amount))) { + MOZ_COLLECT_REPORT( + "private", KIND_OTHER, UNITS_BYTES, amount, +"Memory that cannot be shared with other processes, including memory that is " +"committed and marked MEM_PRIVATE, data that is not mapped, and executable " +"pages that have been written to."); + } + return NS_OK; + } +}; +NS_IMPL_ISUPPORTS(PrivateReporter, nsIMemoryReporter) +#endif + +#ifdef HAVE_VSIZE_AND_RESIDENT_REPORTERS +class VsizeReporter final : public nsIMemoryReporter +{ + ~VsizeReporter() {} + +public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override + { + int64_t amount; + if (NS_SUCCEEDED(VsizeDistinguishedAmount(&amount))) { + MOZ_COLLECT_REPORT( + "vsize", KIND_OTHER, UNITS_BYTES, amount, +"Memory mapped by the process, including code and data segments, the heap, " +"thread stacks, memory explicitly mapped by the process via mmap and similar " +"operations, and memory shared with other processes. This is the vsize figure " +"as reported by 'top' and 'ps'. This figure is of limited use on Mac, where " +"processes share huge amounts of memory with one another. But even on other " +"operating systems, 'resident' is a much better measure of the memory " +"resources used by the process."); + } + return NS_OK; + } +}; +NS_IMPL_ISUPPORTS(VsizeReporter, nsIMemoryReporter) + +class ResidentReporter final : public nsIMemoryReporter +{ + ~ResidentReporter() {} + +public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override + { + int64_t amount; + if (NS_SUCCEEDED(ResidentDistinguishedAmount(&amount))) { + MOZ_COLLECT_REPORT( + "resident", KIND_OTHER, UNITS_BYTES, amount, +"Memory mapped by the process that is present in physical memory, also known " +"as the resident set size (RSS). This is the best single figure to use when " +"considering the memory resources used by the process, but it depends both on " +"other processes being run and details of the OS kernel and so is best used " +"for comparing the memory usage of a single process at different points in " +"time."); + } + return NS_OK; + } +}; +NS_IMPL_ISUPPORTS(ResidentReporter, nsIMemoryReporter) + +#endif // HAVE_VSIZE_AND_RESIDENT_REPORTERS + +#ifdef HAVE_RESIDENT_UNIQUE_REPORTER +class ResidentUniqueReporter final : public nsIMemoryReporter +{ + ~ResidentUniqueReporter() {} + +public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override + { + int64_t amount = 0; + if (NS_SUCCEEDED(ResidentUniqueDistinguishedAmount(&amount))) { + MOZ_COLLECT_REPORT( + "resident-unique", KIND_OTHER, UNITS_BYTES, amount, +"Memory mapped by the process that is present in physical memory and not " +"shared with any other processes. This is also known as the process's unique " +"set size (USS). This is the amount of RAM we'd expect to be freed if we " +"closed this process."); + } + return NS_OK; + } +}; +NS_IMPL_ISUPPORTS(ResidentUniqueReporter, nsIMemoryReporter) + +#endif // HAVE_RESIDENT_UNIQUE_REPORTER + +#ifdef HAVE_SYSTEM_HEAP_REPORTER + +class SystemHeapReporter final : public nsIMemoryReporter +{ + ~SystemHeapReporter() {} + +public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override + { + int64_t amount; + if (NS_SUCCEEDED(SystemHeapSize(&amount))) { + MOZ_COLLECT_REPORT( + "system-heap-allocated", KIND_OTHER, UNITS_BYTES, amount, +"Memory used by the system allocator that is currently allocated to the " +"application. This is distinct from the jemalloc heap that Firefox uses for " +"most or all of its heap allocations. Ideally this number is zero, but " +"on some platforms we cannot force every heap allocation through jemalloc."); + } + return NS_OK; + } +}; +NS_IMPL_ISUPPORTS(SystemHeapReporter, nsIMemoryReporter) + +#endif // HAVE_SYSTEM_HEAP_REPORTER + +#ifdef XP_UNIX + +#include <sys/resource.h> + +#define HAVE_RESIDENT_PEAK_REPORTER 1 + +static MOZ_MUST_USE nsresult +ResidentPeakDistinguishedAmount(int64_t* aN) +{ + struct rusage usage; + if (0 == getrusage(RUSAGE_SELF, &usage)) { + // The units for ru_maxrrs: + // - Mac: bytes + // - Solaris: pages? But some sources it actually always returns 0, so + // check for that + // - Linux, {Net/Open/Free}BSD, DragonFly: KiB +#ifdef XP_MACOSX + *aN = usage.ru_maxrss; +#elif defined(SOLARIS) + *aN = usage.ru_maxrss * getpagesize(); +#else + *aN = usage.ru_maxrss * 1024; +#endif + if (*aN > 0) { + return NS_OK; + } + } + return NS_ERROR_FAILURE; +} + +class ResidentPeakReporter final : public nsIMemoryReporter +{ + ~ResidentPeakReporter() {} + +public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override + { + int64_t amount = 0; + if (NS_SUCCEEDED(ResidentPeakDistinguishedAmount(&amount))) { + MOZ_COLLECT_REPORT( + "resident-peak", KIND_OTHER, UNITS_BYTES, amount, +"The peak 'resident' value for the lifetime of the process."); + } + return NS_OK; + } +}; +NS_IMPL_ISUPPORTS(ResidentPeakReporter, nsIMemoryReporter) + +#define HAVE_PAGE_FAULT_REPORTERS 1 + +class PageFaultsSoftReporter final : public nsIMemoryReporter +{ + ~PageFaultsSoftReporter() {} + +public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override + { + struct rusage usage; + int err = getrusage(RUSAGE_SELF, &usage); + if (err == 0) { + int64_t amount = usage.ru_minflt; + MOZ_COLLECT_REPORT( + "page-faults-soft", KIND_OTHER, UNITS_COUNT_CUMULATIVE, amount, +"The number of soft page faults (also known as 'minor page faults') that " +"have occurred since the process started. A soft page fault occurs when the " +"process tries to access a page which is present in physical memory but is " +"not mapped into the process's address space. For instance, a process might " +"observe soft page faults when it loads a shared library which is already " +"present in physical memory. A process may experience many thousands of soft " +"page faults even when the machine has plenty of available physical memory, " +"and because the OS services a soft page fault without accessing the disk, " +"they impact performance much less than hard page faults."); + } + return NS_OK; + } +}; +NS_IMPL_ISUPPORTS(PageFaultsSoftReporter, nsIMemoryReporter) + +static MOZ_MUST_USE nsresult +PageFaultsHardDistinguishedAmount(int64_t* aAmount) +{ + struct rusage usage; + int err = getrusage(RUSAGE_SELF, &usage); + if (err != 0) { + return NS_ERROR_FAILURE; + } + *aAmount = usage.ru_majflt; + return NS_OK; +} + +class PageFaultsHardReporter final : public nsIMemoryReporter +{ + ~PageFaultsHardReporter() {} + +public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override + { + int64_t amount = 0; + if (NS_SUCCEEDED(PageFaultsHardDistinguishedAmount(&amount))) { + MOZ_COLLECT_REPORT( + "page-faults-hard", KIND_OTHER, UNITS_COUNT_CUMULATIVE, amount, +"The number of hard page faults (also known as 'major page faults') that have " +"occurred since the process started. A hard page fault occurs when a process " +"tries to access a page which is not present in physical memory. The " +"operating system must access the disk in order to fulfill a hard page fault. " +"When memory is plentiful, you should see very few hard page faults. But if " +"the process tries to use more memory than your machine has available, you " +"may see many thousands of hard page faults. Because accessing the disk is up " +"to a million times slower than accessing RAM, the program may run very " +"slowly when it is experiencing more than 100 or so hard page faults a " +"second."); + } + return NS_OK; + } +}; +NS_IMPL_ISUPPORTS(PageFaultsHardReporter, nsIMemoryReporter) + +#endif // XP_UNIX + +/** + ** memory reporter implementation for jemalloc and OSX malloc, + ** to obtain info on total memory in use (that we know about, + ** at least -- on OSX, there are sometimes other zones in use). + **/ + +#ifdef HAVE_JEMALLOC_STATS + +static size_t +HeapOverhead(jemalloc_stats_t* aStats) +{ + return aStats->waste + aStats->bookkeeping + + aStats->page_cache + aStats->bin_unused; +} + +// This has UNITS_PERCENTAGE, so it is multiplied by 100x *again* on top of the +// 100x for the percentage. +static int64_t +HeapOverheadFraction(jemalloc_stats_t* aStats) +{ + size_t heapOverhead = HeapOverhead(aStats); + size_t heapCommitted = aStats->allocated + heapOverhead; + return int64_t(10000 * (heapOverhead / (double)heapCommitted)); +} + +class JemallocHeapReporter final : public nsIMemoryReporter +{ + ~JemallocHeapReporter() {} + +public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override + { + jemalloc_stats_t stats; + jemalloc_stats(&stats); + + MOZ_COLLECT_REPORT( + "heap-committed/allocated", KIND_OTHER, UNITS_BYTES, stats.allocated, +"Memory mapped by the heap allocator that is currently allocated to the " +"application. This may exceed the amount of memory requested by the " +"application because the allocator regularly rounds up request sizes. (The " +"exact amount requested is not recorded.)"); + + MOZ_COLLECT_REPORT( + "heap-allocated", KIND_OTHER, UNITS_BYTES, stats.allocated, +"The same as 'heap-committed/allocated'."); + + // We mark this and the other heap-overhead reporters as KIND_NONHEAP + // because KIND_HEAP memory means "counted in heap-allocated", which + // this is not. + MOZ_COLLECT_REPORT( + "explicit/heap-overhead/bin-unused", KIND_NONHEAP, UNITS_BYTES, + stats.bin_unused, +"Unused bytes due to fragmentation in the bins used for 'small' (<= 2 KiB) " +"allocations. These bytes will be used if additional allocations occur."); + + if (stats.waste > 0) { + MOZ_COLLECT_REPORT( + "explicit/heap-overhead/waste", KIND_NONHEAP, UNITS_BYTES, + stats.waste, +"Committed bytes which do not correspond to an active allocation and which the " +"allocator is not intentionally keeping alive (i.e., not " +"'explicit/heap-overhead/{bookkeeping,page-cache,bin-unused}')."); + } + + MOZ_COLLECT_REPORT( + "explicit/heap-overhead/bookkeeping", KIND_NONHEAP, UNITS_BYTES, + stats.bookkeeping, +"Committed bytes which the heap allocator uses for internal data structures."); + + MOZ_COLLECT_REPORT( + "explicit/heap-overhead/page-cache", KIND_NONHEAP, UNITS_BYTES, + stats.page_cache, +"Memory which the allocator could return to the operating system, but hasn't. " +"The allocator keeps this memory around as an optimization, so it doesn't " +"have to ask the OS the next time it needs to fulfill a request. This value " +"is typically not larger than a few megabytes."); + + MOZ_COLLECT_REPORT( + "heap-committed/overhead", KIND_OTHER, UNITS_BYTES, + HeapOverhead(&stats), +"The sum of 'explicit/heap-overhead/*'."); + + MOZ_COLLECT_REPORT( + "heap-mapped", KIND_OTHER, UNITS_BYTES, stats.mapped, +"Amount of memory currently mapped. Includes memory that is uncommitted, i.e. " +"neither in physical memory nor paged to disk."); + + MOZ_COLLECT_REPORT( + "heap-chunksize", KIND_OTHER, UNITS_BYTES, stats.chunksize, + "Size of chunks."); + + return NS_OK; + } +}; +NS_IMPL_ISUPPORTS(JemallocHeapReporter, nsIMemoryReporter) + +#endif // HAVE_JEMALLOC_STATS + +// Why is this here? At first glance, you'd think it could be defined and +// registered with nsMemoryReporterManager entirely within nsAtomTable.cpp. +// However, the obvious time to register it is when the table is initialized, +// and that happens before XPCOM components are initialized, which means the +// RegisterStrongMemoryReporter call fails. So instead we do it here. +class AtomTablesReporter final : public nsIMemoryReporter +{ + MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf) + + ~AtomTablesReporter() {} + +public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override + { + size_t Main, Static; + NS_SizeOfAtomTablesIncludingThis(MallocSizeOf, &Main, &Static); + + MOZ_COLLECT_REPORT( + "explicit/atom-tables/main", KIND_HEAP, UNITS_BYTES, Main, + "Memory used by the main atoms table."); + + MOZ_COLLECT_REPORT( + "explicit/atom-tables/static", KIND_HEAP, UNITS_BYTES, Static, + "Memory used by the static atoms table."); + + return NS_OK; + } +}; +NS_IMPL_ISUPPORTS(AtomTablesReporter, nsIMemoryReporter) + +#ifdef DEBUG + +// Ideally, this would be implemented in BlockingResourceBase.cpp. +// However, this ends up breaking the linking step of various unit tests due +// to adding a new dependency to libdmd for a commonly used feature (mutexes) +// in DMD builds. So instead we do it here. +class DeadlockDetectorReporter final : public nsIMemoryReporter +{ + MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf) + + ~DeadlockDetectorReporter() {} + +public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override + { + MOZ_COLLECT_REPORT( + "explicit/deadlock-detector", KIND_HEAP, UNITS_BYTES, + BlockingResourceBase::SizeOfDeadlockDetector(MallocSizeOf), + "Memory used by the deadlock detector."); + + return NS_OK; + } +}; +NS_IMPL_ISUPPORTS(DeadlockDetectorReporter, nsIMemoryReporter) + +#endif + +#ifdef MOZ_DMD + +namespace mozilla { +namespace dmd { + +class DMDReporter final : public nsIMemoryReporter +{ +public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override + { + dmd::Sizes sizes; + dmd::SizeOf(&sizes); + + MOZ_COLLECT_REPORT( + "explicit/dmd/stack-traces/used", KIND_HEAP, UNITS_BYTES, + sizes.mStackTracesUsed, + "Memory used by stack traces which correspond to at least " + "one heap block DMD is tracking."); + + MOZ_COLLECT_REPORT( + "explicit/dmd/stack-traces/unused", KIND_HEAP, UNITS_BYTES, + sizes.mStackTracesUnused, + "Memory used by stack traces which don't correspond to any heap " + "blocks DMD is currently tracking."); + + MOZ_COLLECT_REPORT( + "explicit/dmd/stack-traces/table", KIND_HEAP, UNITS_BYTES, + sizes.mStackTraceTable, + "Memory used by DMD's stack trace table."); + + MOZ_COLLECT_REPORT( + "explicit/dmd/live-block-table", KIND_HEAP, UNITS_BYTES, + sizes.mLiveBlockTable, + "Memory used by DMD's live block table."); + + MOZ_COLLECT_REPORT( + "explicit/dmd/dead-block-list", KIND_HEAP, UNITS_BYTES, + sizes.mDeadBlockTable, + "Memory used by DMD's dead block list."); + + return NS_OK; + } + +private: + ~DMDReporter() {} +}; +NS_IMPL_ISUPPORTS(DMDReporter, nsIMemoryReporter) + +} // namespace dmd +} // namespace mozilla + +#endif // MOZ_DMD + +/** + ** nsMemoryReporterManager implementation + **/ + +NS_IMPL_ISUPPORTS(nsMemoryReporterManager, nsIMemoryReporterManager) + +NS_IMETHODIMP +nsMemoryReporterManager::Init() +{ + if (!NS_IsMainThread()) { + MOZ_CRASH(); + } + + // Under normal circumstances this function is only called once. However, + // we've (infrequently) seen memory report dumps in crash reports that + // suggest that this function is sometimes called multiple times. That in + // turn means that multiple reporters of each kind are registered, which + // leads to duplicated reports of individual measurements such as "resident", + // "vsize", etc. + // + // It's unclear how these multiple calls can occur. The only plausible theory + // so far is badly-written extensions, because this function is callable from + // JS code via nsIMemoryReporter.idl. + // + // Whatever the cause, it's a bad thing. So we protect against it with the + // following check. + static bool isInited = false; + if (isInited) { + NS_WARNING("nsMemoryReporterManager::Init() has already been called!"); + return NS_OK; + } + isInited = true; + +#if defined(HAVE_JEMALLOC_STATS) && defined(MOZ_GLUE_IN_PROGRAM) + if (!jemalloc_stats) { + return NS_ERROR_FAILURE; + } +#endif + +#ifdef HAVE_JEMALLOC_STATS + RegisterStrongReporter(new JemallocHeapReporter()); +#endif + +#ifdef HAVE_VSIZE_AND_RESIDENT_REPORTERS + RegisterStrongReporter(new VsizeReporter()); + RegisterStrongReporter(new ResidentReporter()); +#endif + +#ifdef HAVE_VSIZE_MAX_CONTIGUOUS_REPORTER + RegisterStrongReporter(new VsizeMaxContiguousReporter()); +#endif + +#ifdef HAVE_RESIDENT_PEAK_REPORTER + RegisterStrongReporter(new ResidentPeakReporter()); +#endif + +#ifdef HAVE_RESIDENT_UNIQUE_REPORTER + RegisterStrongReporter(new ResidentUniqueReporter()); +#endif + +#ifdef HAVE_PAGE_FAULT_REPORTERS + RegisterStrongReporter(new PageFaultsSoftReporter()); + RegisterStrongReporter(new PageFaultsHardReporter()); +#endif + +#ifdef HAVE_PRIVATE_REPORTER + RegisterStrongReporter(new PrivateReporter()); +#endif + +#ifdef HAVE_SYSTEM_HEAP_REPORTER + RegisterStrongReporter(new SystemHeapReporter()); +#endif + + RegisterStrongReporter(new AtomTablesReporter()); + +#ifdef DEBUG + RegisterStrongReporter(new DeadlockDetectorReporter()); +#endif + +#ifdef MOZ_DMD + RegisterStrongReporter(new mozilla::dmd::DMDReporter()); +#endif + +#ifdef XP_WIN + RegisterStrongReporter(new WindowsAddressSpaceReporter()); +#endif + +#ifdef XP_UNIX + nsMemoryInfoDumper::Initialize(); +#endif + + return NS_OK; +} + +nsMemoryReporterManager::nsMemoryReporterManager() + : mMutex("nsMemoryReporterManager::mMutex") + , mIsRegistrationBlocked(false) + , mStrongReporters(new StrongReportersTable()) + , mWeakReporters(new WeakReportersTable()) + , mSavedStrongReporters(nullptr) + , mSavedWeakReporters(nullptr) + , mNextGeneration(1) + , mPendingProcessesState(nullptr) + , mPendingReportersState(nullptr) +{ +} + +nsMemoryReporterManager::~nsMemoryReporterManager() +{ + delete mStrongReporters; + delete mWeakReporters; + NS_ASSERTION(!mSavedStrongReporters, "failed to restore strong reporters"); + NS_ASSERTION(!mSavedWeakReporters, "failed to restore weak reporters"); +} + +#ifdef MOZ_WIDGET_GONK +#define DEBUG_CHILD_PROCESS_MEMORY_REPORTING 1 +#endif + +#ifdef DEBUG_CHILD_PROCESS_MEMORY_REPORTING +#define MEMORY_REPORTING_LOG(format, ...) \ + printf_stderr("++++ MEMORY REPORTING: " format, ##__VA_ARGS__); +#else +#define MEMORY_REPORTING_LOG(...) +#endif + +NS_IMETHODIMP +nsMemoryReporterManager::GetReports( + nsIHandleReportCallback* aHandleReport, + nsISupports* aHandleReportData, + nsIFinishReportingCallback* aFinishReporting, + nsISupports* aFinishReportingData, + bool aAnonymize) +{ + return GetReportsExtended(aHandleReport, aHandleReportData, + aFinishReporting, aFinishReportingData, + aAnonymize, + /* minimize = */ false, + /* DMDident = */ EmptyString()); +} + +NS_IMETHODIMP +nsMemoryReporterManager::GetReportsExtended( + nsIHandleReportCallback* aHandleReport, + nsISupports* aHandleReportData, + nsIFinishReportingCallback* aFinishReporting, + nsISupports* aFinishReportingData, + bool aAnonymize, + bool aMinimize, + const nsAString& aDMDDumpIdent) +{ + nsresult rv; + + // Memory reporters are not necessarily threadsafe, so this function must + // be called from the main thread. + if (!NS_IsMainThread()) { + MOZ_CRASH(); + } + + uint32_t generation = mNextGeneration++; + + if (mPendingProcessesState) { + // A request is in flight. Don't start another one. And don't report + // an error; just ignore it, and let the in-flight request finish. + MEMORY_REPORTING_LOG("GetReports (gen=%u, s->gen=%u): abort\n", + generation, mPendingProcessesState->mGeneration); + return NS_OK; + } + + MEMORY_REPORTING_LOG("GetReports (gen=%u)\n", generation); + + uint32_t concurrency = Preferences::GetUint("memory.report_concurrency", 1); + MOZ_ASSERT(concurrency >= 1); + if (concurrency < 1) { + concurrency = 1; + } + mPendingProcessesState = new PendingProcessesState(generation, + aAnonymize, + aMinimize, + concurrency, + aHandleReport, + aHandleReportData, + aFinishReporting, + aFinishReportingData, + aDMDDumpIdent); + + if (aMinimize) { + nsCOMPtr<nsIRunnable> callback = + NewRunnableMethod(this, &nsMemoryReporterManager::StartGettingReports); + rv = MinimizeMemoryUsage(callback); + } else { + rv = StartGettingReports(); + } + return rv; +} + +nsresult +nsMemoryReporterManager::StartGettingReports() +{ + PendingProcessesState* s = mPendingProcessesState; + nsresult rv; + + // Get reports for this process. + FILE* parentDMDFile = nullptr; +#ifdef MOZ_DMD + if (!s->mDMDDumpIdent.IsEmpty()) { + rv = nsMemoryInfoDumper::OpenDMDFile(s->mDMDDumpIdent, getpid(), + &parentDMDFile); + if (NS_WARN_IF(NS_FAILED(rv))) { + // Proceed with the memory report as if DMD were disabled. + parentDMDFile = nullptr; + } + } +#endif + + // This is async. + GetReportsForThisProcessExtended(s->mHandleReport, s->mHandleReportData, + s->mAnonymize, parentDMDFile, + s->mFinishReporting, s->mFinishReportingData); + + nsTArray<ContentParent*> childWeakRefs; + ContentParent::GetAll(childWeakRefs); + if (!childWeakRefs.IsEmpty()) { + // Request memory reports from child processes. This happens + // after the parent report so that the parent's main thread will + // be free to process the child reports, instead of causing them + // to be buffered and consume (possibly scarce) memory. + + for (size_t i = 0; i < childWeakRefs.Length(); ++i) { + s->mChildrenPending.AppendElement(childWeakRefs[i]); + } + + nsCOMPtr<nsITimer> timer = do_CreateInstance(NS_TIMER_CONTRACTID); + // Don't use NS_ENSURE_* here; can't return until the report is finished. + if (NS_WARN_IF(!timer)) { + FinishReporting(); + return NS_ERROR_FAILURE; + } + rv = timer->InitWithFuncCallback(TimeoutCallback, + this, kTimeoutLengthMS, + nsITimer::TYPE_ONE_SHOT); + if (NS_WARN_IF(NS_FAILED(rv))) { + FinishReporting(); + return rv; + } + + MOZ_ASSERT(!s->mTimer); + s->mTimer.swap(timer); + } + + return NS_OK; +} + +void +nsMemoryReporterManager::DispatchReporter( + nsIMemoryReporter* aReporter, bool aIsAsync, + nsIHandleReportCallback* aHandleReport, + nsISupports* aHandleReportData, + bool aAnonymize) +{ + MOZ_ASSERT(mPendingReportersState); + + // Grab refs to everything used in the lambda function. + RefPtr<nsMemoryReporterManager> self = this; + nsCOMPtr<nsIMemoryReporter> reporter = aReporter; + nsCOMPtr<nsIHandleReportCallback> handleReport = aHandleReport; + nsCOMPtr<nsISupports> handleReportData = aHandleReportData; + + nsCOMPtr<nsIRunnable> event = NS_NewRunnableFunction( + [self, reporter, aIsAsync, handleReport, handleReportData, aAnonymize] () { + reporter->CollectReports(handleReport, handleReportData, aAnonymize); + if (!aIsAsync) { + self->EndReport(); + } + }); + + NS_DispatchToMainThread(event); + mPendingReportersState->mReportsPending++; +} + +NS_IMETHODIMP +nsMemoryReporterManager::GetReportsForThisProcessExtended( + nsIHandleReportCallback* aHandleReport, nsISupports* aHandleReportData, + bool aAnonymize, FILE* aDMDFile, + nsIFinishReportingCallback* aFinishReporting, + nsISupports* aFinishReportingData) +{ + // Memory reporters are not necessarily threadsafe, so this function must + // be called from the main thread. + if (!NS_IsMainThread()) { + MOZ_CRASH(); + } + + if (NS_WARN_IF(mPendingReportersState)) { + // Report is already in progress. + return NS_ERROR_IN_PROGRESS; + } + +#ifdef MOZ_DMD + if (aDMDFile) { + // Clear DMD's reportedness state before running the memory + // reporters, to avoid spurious twice-reported warnings. + dmd::ClearReports(); + } +#else + MOZ_ASSERT(!aDMDFile); +#endif + + mPendingReportersState = new PendingReportersState( + aFinishReporting, aFinishReportingData, aDMDFile); + + { + mozilla::MutexAutoLock autoLock(mMutex); + + for (auto iter = mStrongReporters->Iter(); !iter.Done(); iter.Next()) { + DispatchReporter(iter.Key(), iter.Data(), + aHandleReport, aHandleReportData, aAnonymize); + } + + for (auto iter = mWeakReporters->Iter(); !iter.Done(); iter.Next()) { + nsCOMPtr<nsIMemoryReporter> reporter = iter.Key(); + DispatchReporter(reporter, iter.Data(), + aHandleReport, aHandleReportData, aAnonymize); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMemoryReporterManager::EndReport() +{ + if (--mPendingReportersState->mReportsPending == 0) { +#ifdef MOZ_DMD + if (mPendingReportersState->mDMDFile) { + nsMemoryInfoDumper::DumpDMDToFile(mPendingReportersState->mDMDFile); + } +#endif + if (mPendingProcessesState) { + // This is the parent process. + EndProcessReport(mPendingProcessesState->mGeneration, true); + } else { + mPendingReportersState->mFinishReporting->Callback( + mPendingReportersState->mFinishReportingData); + } + + delete mPendingReportersState; + mPendingReportersState = nullptr; + } + + return NS_OK; +} + +nsMemoryReporterManager::PendingProcessesState* +nsMemoryReporterManager::GetStateForGeneration(uint32_t aGeneration) +{ + // Memory reporting only happens on the main thread. + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + + PendingProcessesState* s = mPendingProcessesState; + + if (!s) { + // If we reach here, then: + // + // - A child process reported back too late, and no subsequent request + // is in flight. + // + // So there's nothing to be done. Just ignore it. + MEMORY_REPORTING_LOG( + "HandleChildReports: no request in flight (aGen=%u)\n", + aGeneration); + return nullptr; + } + + if (aGeneration != s->mGeneration) { + // If we reach here, a child process must have reported back, too late, + // while a subsequent (higher-numbered) request is in flight. Again, + // ignore it. + MOZ_ASSERT(aGeneration < s->mGeneration); + MEMORY_REPORTING_LOG( + "HandleChildReports: gen mismatch (aGen=%u, s->gen=%u)\n", + aGeneration, s->mGeneration); + return nullptr; + } + + return s; +} + +// This function has no return value. If something goes wrong, there's no +// clear place to report the problem to, but that's ok -- we will end up +// hitting the timeout and executing TimeoutCallback(). +void +nsMemoryReporterManager::HandleChildReport( + uint32_t aGeneration, + const dom::MemoryReport& aChildReport) +{ + PendingProcessesState* s = GetStateForGeneration(aGeneration); + if (!s) { + return; + } + + // Child reports should have a non-empty process. + MOZ_ASSERT(!aChildReport.process().IsEmpty()); + + // If the call fails, ignore and continue. + s->mHandleReport->Callback(aChildReport.process(), + aChildReport.path(), + aChildReport.kind(), + aChildReport.units(), + aChildReport.amount(), + aChildReport.desc(), + s->mHandleReportData); +} + +/* static */ bool +nsMemoryReporterManager::StartChildReport(mozilla::dom::ContentParent* aChild, + const PendingProcessesState* aState) +{ + if (!aChild->IsAlive()) { + MEMORY_REPORTING_LOG("StartChildReports (gen=%u): child exited before" + " its report was started\n", + aState->mGeneration); + return false; + } + + mozilla::dom::MaybeFileDesc dmdFileDesc = void_t(); +#ifdef MOZ_DMD + if (!aState->mDMDDumpIdent.IsEmpty()) { + FILE *dmdFile = nullptr; + nsresult rv = nsMemoryInfoDumper::OpenDMDFile(aState->mDMDDumpIdent, + aChild->Pid(), &dmdFile); + if (NS_WARN_IF(NS_FAILED(rv))) { + // Proceed with the memory report as if DMD were disabled. + dmdFile = nullptr; + } + if (dmdFile) { + dmdFileDesc = mozilla::ipc::FILEToFileDescriptor(dmdFile); + fclose(dmdFile); + } + } +#endif + return aChild->SendPMemoryReportRequestConstructor( + aState->mGeneration, aState->mAnonymize, aState->mMinimize, dmdFileDesc); +} + +void +nsMemoryReporterManager::EndProcessReport(uint32_t aGeneration, bool aSuccess) +{ + PendingProcessesState* s = GetStateForGeneration(aGeneration); + if (!s) { + return; + } + + MOZ_ASSERT(s->mNumProcessesRunning > 0); + s->mNumProcessesRunning--; + s->mNumProcessesCompleted++; + MEMORY_REPORTING_LOG("HandleChildReports (aGen=%u): process %u %s" + " (%u running, %u pending)\n", + aGeneration, s->mNumProcessesCompleted, + aSuccess ? "completed" : "exited during report", + s->mNumProcessesRunning, + static_cast<unsigned>(s->mChildrenPending.Length())); + + // Start pending children up to the concurrency limit. + while (s->mNumProcessesRunning < s->mConcurrencyLimit && + !s->mChildrenPending.IsEmpty()) { + // Pop last element from s->mChildrenPending + RefPtr<ContentParent> nextChild; + nextChild.swap(s->mChildrenPending.LastElement()); + s->mChildrenPending.TruncateLength(s->mChildrenPending.Length() - 1); + // Start report (if the child is still alive). + if (StartChildReport(nextChild, s)) { + ++s->mNumProcessesRunning; + MEMORY_REPORTING_LOG("HandleChildReports (aGen=%u): started child report" + " (%u running, %u pending)\n", + aGeneration, s->mNumProcessesRunning, + static_cast<unsigned>(s->mChildrenPending.Length())); + } + } + + // If all the child processes (if any) have reported, we can cancel + // the timer (if started) and finish up. Otherwise, just return. + if (s->mNumProcessesRunning == 0) { + MOZ_ASSERT(s->mChildrenPending.IsEmpty()); + if (s->mTimer) { + s->mTimer->Cancel(); + } + FinishReporting(); + } +} + +/* static */ void +nsMemoryReporterManager::TimeoutCallback(nsITimer* aTimer, void* aData) +{ + nsMemoryReporterManager* mgr = static_cast<nsMemoryReporterManager*>(aData); + PendingProcessesState* s = mgr->mPendingProcessesState; + + // Release assert because: if the pointer is null we're about to + // crash regardless of DEBUG, and this way the compiler doesn't + // complain about unused variables. + MOZ_RELEASE_ASSERT(s, "mgr->mPendingProcessesState"); + MEMORY_REPORTING_LOG("TimeoutCallback (s->gen=%u; %u running, %u pending)\n", + s->mGeneration, s->mNumProcessesRunning, + static_cast<unsigned>(s->mChildrenPending.Length())); + + // We don't bother sending any kind of cancellation message to the child + // processes that haven't reported back. + mgr->FinishReporting(); +} + +nsresult +nsMemoryReporterManager::FinishReporting() +{ + // Memory reporting only happens on the main thread. + if (!NS_IsMainThread()) { + MOZ_CRASH(); + } + + MOZ_ASSERT(mPendingProcessesState); + MEMORY_REPORTING_LOG("FinishReporting (s->gen=%u; %u processes reported)\n", + mPendingProcessesState->mGeneration, + mPendingProcessesState->mNumProcessesCompleted); + + // Call this before deleting |mPendingProcessesState|. That way, if + // |mFinishReportData| calls GetReports(), it will silently abort, as + // required. + nsresult rv = mPendingProcessesState->mFinishReporting->Callback( + mPendingProcessesState->mFinishReportingData); + + delete mPendingProcessesState; + mPendingProcessesState = nullptr; + return rv; +} + +nsMemoryReporterManager::PendingProcessesState::PendingProcessesState( + uint32_t aGeneration, bool aAnonymize, bool aMinimize, + uint32_t aConcurrencyLimit, + nsIHandleReportCallback* aHandleReport, + nsISupports* aHandleReportData, + nsIFinishReportingCallback* aFinishReporting, + nsISupports* aFinishReportingData, + const nsAString& aDMDDumpIdent) + : mGeneration(aGeneration) + , mAnonymize(aAnonymize) + , mMinimize(aMinimize) + , mChildrenPending() + , mNumProcessesRunning(1) // reporting starts with the parent + , mNumProcessesCompleted(0) + , mConcurrencyLimit(aConcurrencyLimit) + , mHandleReport(aHandleReport) + , mHandleReportData(aHandleReportData) + , mFinishReporting(aFinishReporting) + , mFinishReportingData(aFinishReportingData) + , mDMDDumpIdent(aDMDDumpIdent) +{ +} + +static void +CrashIfRefcountIsZero(nsISupports* aObj) +{ + // This will probably crash if the object's refcount is 0. + uint32_t refcnt = NS_ADDREF(aObj); + if (refcnt <= 1) { + MOZ_CRASH("CrashIfRefcountIsZero: refcount is zero"); + } + NS_RELEASE(aObj); +} + +nsresult +nsMemoryReporterManager::RegisterReporterHelper( + nsIMemoryReporter* aReporter, bool aForce, bool aStrong, bool aIsAsync) +{ + // This method is thread-safe. + mozilla::MutexAutoLock autoLock(mMutex); + + if (mIsRegistrationBlocked && !aForce) { + return NS_ERROR_FAILURE; + } + + if (mStrongReporters->Contains(aReporter) || + mWeakReporters->Contains(aReporter)) { + return NS_ERROR_FAILURE; + } + + // If |aStrong| is true, |aReporter| may have a refcnt of 0, so we take + // a kung fu death grip before calling PutEntry. Otherwise, if PutEntry + // addref'ed and released |aReporter| before finally addref'ing it for + // good, it would free aReporter! The kung fu death grip could itself be + // problematic if PutEntry didn't addref |aReporter| (because then when the + // death grip goes out of scope, we would delete the reporter). In debug + // mode, we check that this doesn't happen. + // + // If |aStrong| is false, we require that |aReporter| have a non-zero + // refcnt. + // + if (aStrong) { + nsCOMPtr<nsIMemoryReporter> kungFuDeathGrip = aReporter; + mStrongReporters->Put(aReporter, aIsAsync); + CrashIfRefcountIsZero(aReporter); + } else { + CrashIfRefcountIsZero(aReporter); + nsCOMPtr<nsIXPConnectWrappedJS> jsComponent = do_QueryInterface(aReporter); + if (jsComponent) { + // We cannot allow non-native reporters (WrappedJS), since we'll be + // holding onto a raw pointer, which would point to the wrapper, + // and that wrapper is likely to go away as soon as this register + // call finishes. This would then lead to subsequent crashes in + // CollectReports(). + return NS_ERROR_XPC_BAD_CONVERT_JS; + } + mWeakReporters->Put(aReporter, aIsAsync); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMemoryReporterManager::RegisterStrongReporter(nsIMemoryReporter* aReporter) +{ + return RegisterReporterHelper(aReporter, /* force = */ false, + /* strong = */ true, + /* async = */ false); +} + +NS_IMETHODIMP +nsMemoryReporterManager::RegisterStrongAsyncReporter(nsIMemoryReporter* aReporter) +{ + return RegisterReporterHelper(aReporter, /* force = */ false, + /* strong = */ true, + /* async = */ true); +} + +NS_IMETHODIMP +nsMemoryReporterManager::RegisterWeakReporter(nsIMemoryReporter* aReporter) +{ + return RegisterReporterHelper(aReporter, /* force = */ false, + /* strong = */ false, + /* async = */ false); +} + +NS_IMETHODIMP +nsMemoryReporterManager::RegisterWeakAsyncReporter(nsIMemoryReporter* aReporter) +{ + return RegisterReporterHelper(aReporter, /* force = */ false, + /* strong = */ false, + /* async = */ true); +} + +NS_IMETHODIMP +nsMemoryReporterManager::RegisterStrongReporterEvenIfBlocked( + nsIMemoryReporter* aReporter) +{ + return RegisterReporterHelper(aReporter, /* force = */ true, + /* strong = */ true, + /* async = */ false); +} + +NS_IMETHODIMP +nsMemoryReporterManager::UnregisterStrongReporter(nsIMemoryReporter* aReporter) +{ + // This method is thread-safe. + mozilla::MutexAutoLock autoLock(mMutex); + + MOZ_ASSERT(!mWeakReporters->Contains(aReporter)); + + if (mStrongReporters->Contains(aReporter)) { + mStrongReporters->Remove(aReporter); + return NS_OK; + } + + // We don't register new reporters when the block is in place, but we do + // unregister existing reporters. This is so we don't keep holding strong + // references that these reporters aren't expecting (which can keep them + // alive longer than intended). + if (mSavedStrongReporters && mSavedStrongReporters->Contains(aReporter)) { + mSavedStrongReporters->Remove(aReporter); + return NS_OK; + } + + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsMemoryReporterManager::UnregisterWeakReporter(nsIMemoryReporter* aReporter) +{ + // This method is thread-safe. + mozilla::MutexAutoLock autoLock(mMutex); + + MOZ_ASSERT(!mStrongReporters->Contains(aReporter)); + + if (mWeakReporters->Contains(aReporter)) { + mWeakReporters->Remove(aReporter); + return NS_OK; + } + + // We don't register new reporters when the block is in place, but we do + // unregister existing reporters. This is so we don't keep holding weak + // references that the old reporters aren't expecting (which can end up as + // dangling pointers that lead to use-after-frees). + if (mSavedWeakReporters && mSavedWeakReporters->Contains(aReporter)) { + mSavedWeakReporters->Remove(aReporter); + return NS_OK; + } + + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsMemoryReporterManager::BlockRegistrationAndHideExistingReporters() +{ + // This method is thread-safe. + mozilla::MutexAutoLock autoLock(mMutex); + if (mIsRegistrationBlocked) { + return NS_ERROR_FAILURE; + } + mIsRegistrationBlocked = true; + + // Hide the existing reporters, saving them for later restoration. + MOZ_ASSERT(!mSavedStrongReporters); + MOZ_ASSERT(!mSavedWeakReporters); + mSavedStrongReporters = mStrongReporters; + mSavedWeakReporters = mWeakReporters; + mStrongReporters = new StrongReportersTable(); + mWeakReporters = new WeakReportersTable(); + + return NS_OK; +} + +NS_IMETHODIMP +nsMemoryReporterManager::UnblockRegistrationAndRestoreOriginalReporters() +{ + // This method is thread-safe. + mozilla::MutexAutoLock autoLock(mMutex); + if (!mIsRegistrationBlocked) { + return NS_ERROR_FAILURE; + } + + // Banish the current reporters, and restore the hidden ones. + delete mStrongReporters; + delete mWeakReporters; + mStrongReporters = mSavedStrongReporters; + mWeakReporters = mSavedWeakReporters; + mSavedStrongReporters = nullptr; + mSavedWeakReporters = nullptr; + + mIsRegistrationBlocked = false; + return NS_OK; +} + +NS_IMETHODIMP +nsMemoryReporterManager::GetVsize(int64_t* aVsize) +{ +#ifdef HAVE_VSIZE_AND_RESIDENT_REPORTERS + return VsizeDistinguishedAmount(aVsize); +#else + *aVsize = 0; + return NS_ERROR_NOT_AVAILABLE; +#endif +} + +NS_IMETHODIMP +nsMemoryReporterManager::GetVsizeMaxContiguous(int64_t* aAmount) +{ +#ifdef HAVE_VSIZE_MAX_CONTIGUOUS_REPORTER + return VsizeMaxContiguousDistinguishedAmount(aAmount); +#else + *aAmount = 0; + return NS_ERROR_NOT_AVAILABLE; +#endif +} + +NS_IMETHODIMP +nsMemoryReporterManager::GetResident(int64_t* aAmount) +{ +#ifdef HAVE_VSIZE_AND_RESIDENT_REPORTERS + return ResidentDistinguishedAmount(aAmount); +#else + *aAmount = 0; + return NS_ERROR_NOT_AVAILABLE; +#endif +} + +NS_IMETHODIMP +nsMemoryReporterManager::GetResidentFast(int64_t* aAmount) +{ +#ifdef HAVE_VSIZE_AND_RESIDENT_REPORTERS + return ResidentFastDistinguishedAmount(aAmount); +#else + *aAmount = 0; + return NS_ERROR_NOT_AVAILABLE; +#endif +} + +/*static*/ int64_t +nsMemoryReporterManager::ResidentFast() +{ +#ifdef HAVE_VSIZE_AND_RESIDENT_REPORTERS + int64_t amount; + nsresult rv = ResidentFastDistinguishedAmount(&amount); + NS_ENSURE_SUCCESS(rv, 0); + return amount; +#else + return 0; +#endif +} + +NS_IMETHODIMP +nsMemoryReporterManager::GetResidentPeak(int64_t* aAmount) +{ +#ifdef HAVE_RESIDENT_PEAK_REPORTER + return ResidentPeakDistinguishedAmount(aAmount); +#else + *aAmount = 0; + return NS_ERROR_NOT_AVAILABLE; +#endif +} + +/*static*/ int64_t +nsMemoryReporterManager::ResidentPeak() +{ +#ifdef HAVE_RESIDENT_PEAK_REPORTER + int64_t amount = 0; + nsresult rv = ResidentPeakDistinguishedAmount(&amount); + NS_ENSURE_SUCCESS(rv, 0); + return amount; +#else + return 0; +#endif +} + +NS_IMETHODIMP +nsMemoryReporterManager::GetResidentUnique(int64_t* aAmount) +{ +#ifdef HAVE_RESIDENT_UNIQUE_REPORTER + return ResidentUniqueDistinguishedAmount(aAmount); +#else + *aAmount = 0; + return NS_ERROR_NOT_AVAILABLE; +#endif +} + +/*static*/ int64_t +nsMemoryReporterManager::ResidentUnique() +{ +#ifdef HAVE_RESIDENT_UNIQUE_REPORTER + int64_t amount = 0; + nsresult rv = ResidentUniqueDistinguishedAmount(&amount); + NS_ENSURE_SUCCESS(rv, 0); + return amount; +#else + return 0; +#endif +} + +NS_IMETHODIMP +nsMemoryReporterManager::GetHeapAllocated(int64_t* aAmount) +{ +#ifdef HAVE_JEMALLOC_STATS + jemalloc_stats_t stats; + jemalloc_stats(&stats); + *aAmount = stats.allocated; + return NS_OK; +#else + *aAmount = 0; + return NS_ERROR_NOT_AVAILABLE; +#endif +} + +// This has UNITS_PERCENTAGE, so it is multiplied by 100x. +NS_IMETHODIMP +nsMemoryReporterManager::GetHeapOverheadFraction(int64_t* aAmount) +{ +#ifdef HAVE_JEMALLOC_STATS + jemalloc_stats_t stats; + jemalloc_stats(&stats); + *aAmount = HeapOverheadFraction(&stats); + return NS_OK; +#else + *aAmount = 0; + return NS_ERROR_NOT_AVAILABLE; +#endif +} + +static MOZ_MUST_USE nsresult +GetInfallibleAmount(InfallibleAmountFn aAmountFn, int64_t* aAmount) +{ + if (aAmountFn) { + *aAmount = aAmountFn(); + return NS_OK; + } + *aAmount = 0; + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsMemoryReporterManager::GetJSMainRuntimeGCHeap(int64_t* aAmount) +{ + return GetInfallibleAmount(mAmountFns.mJSMainRuntimeGCHeap, aAmount); +} + +NS_IMETHODIMP +nsMemoryReporterManager::GetJSMainRuntimeTemporaryPeak(int64_t* aAmount) +{ + return GetInfallibleAmount(mAmountFns.mJSMainRuntimeTemporaryPeak, aAmount); +} + +NS_IMETHODIMP +nsMemoryReporterManager::GetJSMainRuntimeCompartmentsSystem(int64_t* aAmount) +{ + return GetInfallibleAmount(mAmountFns.mJSMainRuntimeCompartmentsSystem, + aAmount); +} + +NS_IMETHODIMP +nsMemoryReporterManager::GetJSMainRuntimeCompartmentsUser(int64_t* aAmount) +{ + return GetInfallibleAmount(mAmountFns.mJSMainRuntimeCompartmentsUser, + aAmount); +} + +NS_IMETHODIMP +nsMemoryReporterManager::GetImagesContentUsedUncompressed(int64_t* aAmount) +{ + return GetInfallibleAmount(mAmountFns.mImagesContentUsedUncompressed, + aAmount); +} + +NS_IMETHODIMP +nsMemoryReporterManager::GetStorageSQLite(int64_t* aAmount) +{ + return GetInfallibleAmount(mAmountFns.mStorageSQLite, aAmount); +} + +NS_IMETHODIMP +nsMemoryReporterManager::GetLowMemoryEventsVirtual(int64_t* aAmount) +{ + return GetInfallibleAmount(mAmountFns.mLowMemoryEventsVirtual, aAmount); +} + +NS_IMETHODIMP +nsMemoryReporterManager::GetLowMemoryEventsPhysical(int64_t* aAmount) +{ + return GetInfallibleAmount(mAmountFns.mLowMemoryEventsPhysical, aAmount); +} + +NS_IMETHODIMP +nsMemoryReporterManager::GetGhostWindows(int64_t* aAmount) +{ + return GetInfallibleAmount(mAmountFns.mGhostWindows, aAmount); +} + +NS_IMETHODIMP +nsMemoryReporterManager::GetPageFaultsHard(int64_t* aAmount) +{ +#ifdef HAVE_PAGE_FAULT_REPORTERS + return PageFaultsHardDistinguishedAmount(aAmount); +#else + *aAmount = 0; + return NS_ERROR_NOT_AVAILABLE; +#endif +} + +NS_IMETHODIMP +nsMemoryReporterManager::GetHasMozMallocUsableSize(bool* aHas) +{ + void* p = malloc(16); + if (!p) { + return NS_ERROR_OUT_OF_MEMORY; + } + size_t usable = moz_malloc_usable_size(p); + free(p); + *aHas = !!(usable > 0); + return NS_OK; +} + +NS_IMETHODIMP +nsMemoryReporterManager::GetIsDMDEnabled(bool* aIsEnabled) +{ +#ifdef MOZ_DMD + *aIsEnabled = true; +#else + *aIsEnabled = false; +#endif + return NS_OK; +} + +NS_IMETHODIMP +nsMemoryReporterManager::GetIsDMDRunning(bool* aIsRunning) +{ +#ifdef MOZ_DMD + *aIsRunning = dmd::IsRunning(); +#else + *aIsRunning = false; +#endif + return NS_OK; +} + +namespace { + +/** + * This runnable lets us implement + * nsIMemoryReporterManager::MinimizeMemoryUsage(). We fire a heap-minimize + * notification, spin the event loop, and repeat this process a few times. + * + * When this sequence finishes, we invoke the callback function passed to the + * runnable's constructor. + */ +class MinimizeMemoryUsageRunnable : public Runnable +{ +public: + explicit MinimizeMemoryUsageRunnable(nsIRunnable* aCallback) + : mCallback(aCallback) + , mRemainingIters(sNumIters) + { + } + + NS_IMETHOD Run() override + { + nsCOMPtr<nsIObserverService> os = services::GetObserverService(); + if (!os) { + return NS_ERROR_FAILURE; + } + + if (mRemainingIters == 0) { + os->NotifyObservers(nullptr, "after-minimize-memory-usage", + u"MinimizeMemoryUsageRunnable"); + if (mCallback) { + mCallback->Run(); + } + return NS_OK; + } + + os->NotifyObservers(nullptr, "memory-pressure", u"heap-minimize"); + mRemainingIters--; + NS_DispatchToMainThread(this); + + return NS_OK; + } + +private: + // Send sNumIters heap-minimize notifications, spinning the event + // loop after each notification (see bug 610166 comment 12 for an + // explanation), because one notification doesn't cut it. + static const uint32_t sNumIters = 3; + + nsCOMPtr<nsIRunnable> mCallback; + uint32_t mRemainingIters; +}; + +} // namespace + +NS_IMETHODIMP +nsMemoryReporterManager::MinimizeMemoryUsage(nsIRunnable* aCallback) +{ + RefPtr<MinimizeMemoryUsageRunnable> runnable = + new MinimizeMemoryUsageRunnable(aCallback); + + return NS_DispatchToMainThread(runnable); +} + +NS_IMETHODIMP +nsMemoryReporterManager::SizeOfTab(mozIDOMWindowProxy* aTopWindow, + int64_t* aJSObjectsSize, + int64_t* aJSStringsSize, + int64_t* aJSOtherSize, + int64_t* aDomSize, + int64_t* aStyleSize, + int64_t* aOtherSize, + int64_t* aTotalSize, + double* aJSMilliseconds, + double* aNonJSMilliseconds) +{ + nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aTopWindow); + auto* piWindow = nsPIDOMWindowOuter::From(aTopWindow); + if (NS_WARN_IF(!global) || NS_WARN_IF(!piWindow)) { + return NS_ERROR_FAILURE; + } + + TimeStamp t1 = TimeStamp::Now(); + + // Measure JS memory consumption (and possibly some non-JS consumption, via + // |jsPrivateSize|). + size_t jsObjectsSize, jsStringsSize, jsPrivateSize, jsOtherSize; + nsresult rv = mSizeOfTabFns.mJS(global->GetGlobalJSObject(), + &jsObjectsSize, &jsStringsSize, + &jsPrivateSize, &jsOtherSize); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + TimeStamp t2 = TimeStamp::Now(); + + // Measure non-JS memory consumption. + size_t domSize, styleSize, otherSize; + rv = mSizeOfTabFns.mNonJS(piWindow, &domSize, &styleSize, &otherSize); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + TimeStamp t3 = TimeStamp::Now(); + + *aTotalSize = 0; +#define DO(aN, n) { *aN = (n); *aTotalSize += (n); } + DO(aJSObjectsSize, jsObjectsSize); + DO(aJSStringsSize, jsStringsSize); + DO(aJSOtherSize, jsOtherSize); + DO(aDomSize, jsPrivateSize + domSize); + DO(aStyleSize, styleSize); + DO(aOtherSize, otherSize); +#undef DO + + *aJSMilliseconds = (t2 - t1).ToMilliseconds(); + *aNonJSMilliseconds = (t3 - t2).ToMilliseconds(); + + return NS_OK; +} + +namespace mozilla { + +#define GET_MEMORY_REPORTER_MANAGER(mgr) \ + RefPtr<nsMemoryReporterManager> mgr = \ + nsMemoryReporterManager::GetOrCreate(); \ + if (!mgr) { \ + return NS_ERROR_FAILURE; \ + } + +nsresult +RegisterStrongMemoryReporter(nsIMemoryReporter* aReporter) +{ + // Hold a strong reference to the argument to make sure it gets released if + // we return early below. + nsCOMPtr<nsIMemoryReporter> reporter = aReporter; + GET_MEMORY_REPORTER_MANAGER(mgr) + return mgr->RegisterStrongReporter(reporter); +} + +nsresult +RegisterStrongAsyncMemoryReporter(nsIMemoryReporter* aReporter) +{ + // Hold a strong reference to the argument to make sure it gets released if + // we return early below. + nsCOMPtr<nsIMemoryReporter> reporter = aReporter; + GET_MEMORY_REPORTER_MANAGER(mgr) + return mgr->RegisterStrongAsyncReporter(reporter); +} + +nsresult +RegisterWeakMemoryReporter(nsIMemoryReporter* aReporter) +{ + GET_MEMORY_REPORTER_MANAGER(mgr) + return mgr->RegisterWeakReporter(aReporter); +} + +nsresult +RegisterWeakAsyncMemoryReporter(nsIMemoryReporter* aReporter) +{ + GET_MEMORY_REPORTER_MANAGER(mgr) + return mgr->RegisterWeakAsyncReporter(aReporter); +} + +nsresult +UnregisterStrongMemoryReporter(nsIMemoryReporter* aReporter) +{ + GET_MEMORY_REPORTER_MANAGER(mgr) + return mgr->UnregisterStrongReporter(aReporter); +} + +nsresult +UnregisterWeakMemoryReporter(nsIMemoryReporter* aReporter) +{ + GET_MEMORY_REPORTER_MANAGER(mgr) + return mgr->UnregisterWeakReporter(aReporter); +} + +// Macro for generating functions that register distinguished amount functions +// with the memory reporter manager. +#define DEFINE_REGISTER_DISTINGUISHED_AMOUNT(kind, name) \ + nsresult \ + Register##name##DistinguishedAmount(kind##AmountFn aAmountFn) \ + { \ + GET_MEMORY_REPORTER_MANAGER(mgr) \ + mgr->mAmountFns.m##name = aAmountFn; \ + return NS_OK; \ + } + +// Macro for generating functions that unregister distinguished amount +// functions with the memory reporter manager. +#define DEFINE_UNREGISTER_DISTINGUISHED_AMOUNT(name) \ + nsresult \ + Unregister##name##DistinguishedAmount() \ + { \ + GET_MEMORY_REPORTER_MANAGER(mgr) \ + mgr->mAmountFns.m##name = nullptr; \ + return NS_OK; \ + } + +DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, JSMainRuntimeGCHeap) +DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, JSMainRuntimeTemporaryPeak) +DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, JSMainRuntimeCompartmentsSystem) +DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, JSMainRuntimeCompartmentsUser) + +DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, ImagesContentUsedUncompressed) +DEFINE_UNREGISTER_DISTINGUISHED_AMOUNT(ImagesContentUsedUncompressed) + +DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, StorageSQLite) +DEFINE_UNREGISTER_DISTINGUISHED_AMOUNT(StorageSQLite) + +DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, LowMemoryEventsVirtual) +DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, LowMemoryEventsPhysical) + +DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, GhostWindows) + +#undef DEFINE_REGISTER_DISTINGUISHED_AMOUNT +#undef DEFINE_UNREGISTER_DISTINGUISHED_AMOUNT + +#define DEFINE_REGISTER_SIZE_OF_TAB(name) \ + nsresult \ + Register##name##SizeOfTab(name##SizeOfTabFn aSizeOfTabFn) \ + { \ + GET_MEMORY_REPORTER_MANAGER(mgr) \ + mgr->mSizeOfTabFns.m##name = aSizeOfTabFn; \ + return NS_OK; \ + } + +DEFINE_REGISTER_SIZE_OF_TAB(JS); +DEFINE_REGISTER_SIZE_OF_TAB(NonJS); + +#undef DEFINE_REGISTER_SIZE_OF_TAB + +#undef GET_MEMORY_REPORTER_MANAGER + +} // namespace mozilla diff --git a/xpcom/base/nsMemoryReporterManager.h b/xpcom/base/nsMemoryReporterManager.h new file mode 100644 index 000000000..8240cbe34 --- /dev/null +++ b/xpcom/base/nsMemoryReporterManager.h @@ -0,0 +1,288 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsMemoryReporterManager_h__ +#define nsMemoryReporterManager_h__ + +#include "mozilla/Mutex.h" +#include "nsHashKeys.h" +#include "nsIMemoryReporter.h" +#include "nsITimer.h" +#include "nsServiceManagerUtils.h" +#include "nsTHashtable.h" + +namespace mozilla { +namespace dom { +class ContentParent; +class MemoryReport; +} // namespace dom +} // namespace mozilla + +class nsITimer; + +class nsMemoryReporterManager final : public nsIMemoryReporterManager +{ + virtual ~nsMemoryReporterManager(); + +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIMEMORYREPORTERMANAGER + + nsMemoryReporterManager(); + + // Gets the memory reporter manager service. + static nsMemoryReporterManager* GetOrCreate() + { + nsCOMPtr<nsIMemoryReporterManager> imgr = + do_GetService("@mozilla.org/memory-reporter-manager;1"); + return static_cast<nsMemoryReporterManager*>(imgr.get()); + } + + typedef nsDataHashtable<nsRefPtrHashKey<nsIMemoryReporter>, bool> StrongReportersTable; + typedef nsDataHashtable<nsPtrHashKey<nsIMemoryReporter>, bool> WeakReportersTable; + + // Inter-process memory reporting proceeds as follows. + // + // - GetReports() (declared within NS_DECL_NSIMEMORYREPORTERMANAGER) + // synchronously gets memory reports for the current process, sets up some + // state (mPendingProcessesState) for when child processes report back -- + // including a timer -- and starts telling child processes to get memory + // reports. Control then returns to the main event loop. + // + // The number of concurrent child process reports is limited by the pref + // "memory.report_concurrency" in order to prevent the memory overhead of + // memory reporting from causing problems, especially on B2G when swapping + // to compressed RAM; see bug 1154053. + // + // - HandleChildReport() is called (asynchronously) once per child process + // reporter callback. + // + // - EndProcessReport() is called (asynchronously) once per process that + // finishes reporting back, including the parent. If all processes do so + // before time-out, the timer is cancelled. If there are child processes + // whose requests have not yet been sent, they will be started until the + // concurrency limit is (again) reached. + // + // - TimeoutCallback() is called (asynchronously) if all the child processes + // don't respond within the time threshold. + // + // - FinishReporting() finishes things off. It is *always* called -- either + // from EndChildReport() (if all child processes have reported back) or + // from TimeoutCallback() (if time-out occurs). + // + // All operations occur on the main thread. + // + // The above sequence of steps is a "request". A partially-completed request + // is described as "in flight". + // + // Each request has a "generation", a unique number that identifies it. This + // is used to ensure that each reports from a child process corresponds to + // the appropriate request from the parent process. (It's easier to + // implement a generation system than to implement a child report request + // cancellation mechanism.) + // + // Failures are mostly ignored, because it's (a) typically the most sensible + // thing to do, and (b) often hard to do anything else. The following are + // the failure cases of note. + // + // - If a request is made while the previous request is in flight, the new + // request is ignored, as per getReports()'s specification. No error is + // reported, because the previous request will complete soon enough. + // + // - If one or more child processes fail to respond within the time limit, + // things will proceed as if they don't exist. No error is reported, + // because partial information is better than nothing. + // + // - If a child process reports after the time-out occurs, it is ignored. + // (Generation checking will ensure it is ignored even if a subsequent + // request is in flight; this is the main use of generations.) No error + // is reported, because there's nothing sensible to be done about it at + // this late stage. + // + // - If the time-out occurs after a child process has sent some reports but + // before it has signaled completion (see bug 1151597), then what it + // successfully sent will be included, with no explicit indication that it + // is incomplete. + // + // Now, what what happens if a child process is created/destroyed in the + // middle of a request? Well, PendingProcessesState is initialized with an array + // of child process actors as of when the report started. So... + // + // - If a process is created after reporting starts, it won't be sent a + // request for reports. So the reported data will reflect how things were + // when the request began. + // + // - If a process is destroyed before it starts reporting back, the reported + // data will reflect how things are when the request ends. + // + // - If a process is destroyed after it starts reporting back but before it + // finishes, the reported data will contain a partial report for it. + // + // - If a process is destroyed after reporting back, but before all other + // child processes have reported back, it will be included in the reported + // data. So the reported data will reflect how things were when the + // request began. + // + // The inconsistencies between these cases are unfortunate but difficult to + // avoid. It's enough of an edge case to not be worth doing more. + // + void HandleChildReport(uint32_t aGeneration, + const mozilla::dom::MemoryReport& aChildReport); + void EndProcessReport(uint32_t aGeneration, bool aSuccess); + + // Functions that (a) implement distinguished amounts, and (b) are outside of + // this module. + struct AmountFns + { + mozilla::InfallibleAmountFn mJSMainRuntimeGCHeap; + mozilla::InfallibleAmountFn mJSMainRuntimeTemporaryPeak; + mozilla::InfallibleAmountFn mJSMainRuntimeCompartmentsSystem; + mozilla::InfallibleAmountFn mJSMainRuntimeCompartmentsUser; + + mozilla::InfallibleAmountFn mImagesContentUsedUncompressed; + + mozilla::InfallibleAmountFn mStorageSQLite; + + mozilla::InfallibleAmountFn mLowMemoryEventsVirtual; + mozilla::InfallibleAmountFn mLowMemoryEventsPhysical; + + mozilla::InfallibleAmountFn mGhostWindows; + + AmountFns() + { + mozilla::PodZero(this); + } + }; + AmountFns mAmountFns; + + // Convenience function to get RSS easily from other code. This is useful + // when debugging transient memory spikes with printf instrumentation. + static int64_t ResidentFast(); + + // Convenience function to get peak RSS easily from other code. + static int64_t ResidentPeak(); + + // Convenience function to get USS easily from other code. This is useful + // when debugging unshared memory pages for forked processes. + static int64_t ResidentUnique(); + + // Functions that measure per-tab memory consumption. + struct SizeOfTabFns + { + mozilla::JSSizeOfTabFn mJS; + mozilla::NonJSSizeOfTabFn mNonJS; + + SizeOfTabFns() + { + mozilla::PodZero(this); + } + }; + SizeOfTabFns mSizeOfTabFns; + +private: + MOZ_MUST_USE nsresult + RegisterReporterHelper(nsIMemoryReporter* aReporter, + bool aForce, bool aStrongRef, bool aIsAsync); + + MOZ_MUST_USE nsresult StartGettingReports(); + // No MOZ_MUST_USE here because ignoring the result is common and reasonable. + nsresult FinishReporting(); + + void DispatchReporter(nsIMemoryReporter* aReporter, bool aIsAsync, + nsIHandleReportCallback* aHandleReport, + nsISupports* aHandleReportData, + bool aAnonymize); + + static void TimeoutCallback(nsITimer* aTimer, void* aData); + // Note: this timeout needs to be long enough to allow for the + // possibility of DMD reports and/or running on a low-end phone. + static const uint32_t kTimeoutLengthMS = 50000; + + mozilla::Mutex mMutex; + bool mIsRegistrationBlocked; + + StrongReportersTable* mStrongReporters; + WeakReportersTable* mWeakReporters; + + // These two are only used for testing purposes. + StrongReportersTable* mSavedStrongReporters; + WeakReportersTable* mSavedWeakReporters; + + uint32_t mNextGeneration; + + // Used to keep track of state of which processes are currently running and + // waiting to run memory reports. Holds references to parameters needed when + // requesting a memory report and finishing reporting. + struct PendingProcessesState + { + uint32_t mGeneration; + bool mAnonymize; + bool mMinimize; + nsCOMPtr<nsITimer> mTimer; + nsTArray<RefPtr<mozilla::dom::ContentParent>> mChildrenPending; + uint32_t mNumProcessesRunning; + uint32_t mNumProcessesCompleted; + uint32_t mConcurrencyLimit; + nsCOMPtr<nsIHandleReportCallback> mHandleReport; + nsCOMPtr<nsISupports> mHandleReportData; + nsCOMPtr<nsIFinishReportingCallback> mFinishReporting; + nsCOMPtr<nsISupports> mFinishReportingData; + nsString mDMDDumpIdent; + + PendingProcessesState(uint32_t aGeneration, bool aAnonymize, bool aMinimize, + uint32_t aConcurrencyLimit, + nsIHandleReportCallback* aHandleReport, + nsISupports* aHandleReportData, + nsIFinishReportingCallback* aFinishReporting, + nsISupports* aFinishReportingData, + const nsAString& aDMDDumpIdent); + }; + + // Used to keep track of the state of the asynchronously run memory + // reporters. The callback and file handle used when all memory reporters + // have finished are also stored here. + struct PendingReportersState + { + // Number of memory reporters currently running. + uint32_t mReportsPending; + + // Callback for when all memory reporters have completed. + nsCOMPtr<nsIFinishReportingCallback> mFinishReporting; + nsCOMPtr<nsISupports> mFinishReportingData; + + // File handle to write a DMD report to if requested. + FILE* mDMDFile; + + PendingReportersState(nsIFinishReportingCallback* aFinishReporting, + nsISupports* aFinishReportingData, + FILE* aDMDFile) + : mReportsPending(0) + , mFinishReporting(aFinishReporting) + , mFinishReportingData(aFinishReportingData) + , mDMDFile(aDMDFile) + { + } + }; + + // When this is non-null, a request is in flight. Note: We use manual + // new/delete for this because its lifetime doesn't match block scope or + // anything like that. + PendingProcessesState* mPendingProcessesState; + + // This is reinitialized each time a call to GetReports is initiated. + PendingReportersState* mPendingReportersState; + + PendingProcessesState* GetStateForGeneration(uint32_t aGeneration); + static MOZ_MUST_USE bool + StartChildReport(mozilla::dom::ContentParent* aChild, + const PendingProcessesState* aState); +}; + +#define NS_MEMORY_REPORTER_MANAGER_CID \ +{ 0xfb97e4f5, 0x32dd, 0x497a, \ +{ 0xba, 0xa2, 0x7d, 0x1e, 0x55, 0x7, 0x99, 0x10 } } + +#endif // nsMemoryReporterManager_h__ diff --git a/xpcom/base/nsMessageLoop.cpp b/xpcom/base/nsMessageLoop.cpp new file mode 100644 index 000000000..fabf6b9f5 --- /dev/null +++ b/xpcom/base/nsMessageLoop.cpp @@ -0,0 +1,172 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsMessageLoop.h" +#include "mozilla/WeakPtr.h" +#include "base/message_loop.h" +#include "base/task.h" +#include "nsIRunnable.h" +#include "nsITimer.h" +#include "nsCOMPtr.h" +#include "nsAutoPtr.h" +#include "nsComponentManagerUtils.h" +#include "nsThreadUtils.h" + +using namespace mozilla; + +namespace { + +/** + * This Task runs its nsIRunnable when Run() is called, or after + * aEnsureRunsAfterMS milliseconds have elapsed since the object was + * constructed. + * + * Note that the MessageLoop owns this object and will delete it after it calls + * Run(). Tread lightly. + */ +class MessageLoopIdleTask + : public Runnable + , public SupportsWeakPtr<MessageLoopIdleTask> +{ +public: + MOZ_DECLARE_WEAKREFERENCE_TYPENAME(MessageLoopIdleTask) + MessageLoopIdleTask(nsIRunnable* aTask, uint32_t aEnsureRunsAfterMS); + NS_IMETHOD Run() override; + +private: + nsresult Init(uint32_t aEnsureRunsAfterMS); + + nsCOMPtr<nsIRunnable> mTask; + nsCOMPtr<nsITimer> mTimer; + + virtual ~MessageLoopIdleTask() {} +}; + +/** + * This timer callback calls MessageLoopIdleTask::Run() when its timer fires. + * (The timer can't call back into MessageLoopIdleTask directly since that's + * not a refcounted object; it's owned by the MessageLoop.) + * + * We keep a weak reference to the MessageLoopIdleTask, although a raw pointer + * should in theory suffice: When the MessageLoopIdleTask runs (right before + * the MessageLoop deletes it), it cancels its timer. But the weak pointer + * saves us from worrying about an edge case somehow messing us up here. + */ +class MessageLoopTimerCallback + : public nsITimerCallback +{ +public: + explicit MessageLoopTimerCallback(MessageLoopIdleTask* aTask); + + NS_DECL_ISUPPORTS + NS_DECL_NSITIMERCALLBACK + +private: + WeakPtr<MessageLoopIdleTask> mTask; + + virtual ~MessageLoopTimerCallback() {} +}; + +MessageLoopIdleTask::MessageLoopIdleTask(nsIRunnable* aTask, + uint32_t aEnsureRunsAfterMS) + : mTask(aTask) +{ + // Init() really shouldn't fail, but if it does, we schedule our runnable + // immediately, because it's more important to guarantee that we run the task + // eventually than it is to run the task when we're idle. + nsresult rv = Init(aEnsureRunsAfterMS); + if (NS_FAILED(rv)) { + NS_WARNING("Running idle task early because we couldn't initialize our timer."); + NS_DispatchToCurrentThread(mTask); + + mTask = nullptr; + mTimer = nullptr; + } +} + +nsresult +MessageLoopIdleTask::Init(uint32_t aEnsureRunsAfterMS) +{ + mTimer = do_CreateInstance("@mozilla.org/timer;1"); + if (NS_WARN_IF(!mTimer)) { + return NS_ERROR_UNEXPECTED; + } + + RefPtr<MessageLoopTimerCallback> callback = + new MessageLoopTimerCallback(this); + + return mTimer->InitWithCallback(callback, aEnsureRunsAfterMS, + nsITimer::TYPE_ONE_SHOT); +} + +NS_IMETHODIMP +MessageLoopIdleTask::Run() +{ + // Null out our pointers because if Run() was called by the timer, this + // object will be kept alive by the MessageLoop until the MessageLoop calls + // Run(). + + if (mTimer) { + mTimer->Cancel(); + mTimer = nullptr; + } + + if (mTask) { + mTask->Run(); + mTask = nullptr; + } + + return NS_OK; +} + +MessageLoopTimerCallback::MessageLoopTimerCallback(MessageLoopIdleTask* aTask) + : mTask(aTask) +{ +} + +NS_IMETHODIMP +MessageLoopTimerCallback::Notify(nsITimer* aTimer) +{ + // We don't expect to hit the case when the timer fires but mTask has been + // deleted, because mTask should cancel the timer before the mTask is + // deleted. But you never know... + NS_WARNING_ASSERTION(mTask, "This timer shouldn't have fired."); + + if (mTask) { + mTask->Run(); + } + return NS_OK; +} + +NS_IMPL_ISUPPORTS(MessageLoopTimerCallback, nsITimerCallback) + +} // namespace + +NS_IMPL_ISUPPORTS(nsMessageLoop, nsIMessageLoop) + +NS_IMETHODIMP +nsMessageLoop::PostIdleTask(nsIRunnable* aTask, uint32_t aEnsureRunsAfterMS) +{ + // The message loop owns MessageLoopIdleTask and deletes it after calling + // Run(). Be careful... + RefPtr<MessageLoopIdleTask> idle = + new MessageLoopIdleTask(aTask, aEnsureRunsAfterMS); + MessageLoop::current()->PostIdleTask(idle.forget()); + + return NS_OK; +} + +nsresult +nsMessageLoopConstructor(nsISupports* aOuter, + const nsIID& aIID, + void** aInstancePtr) +{ + if (NS_WARN_IF(aOuter)) { + return NS_ERROR_NO_AGGREGATION; + } + nsISupports* messageLoop = new nsMessageLoop(); + return messageLoop->QueryInterface(aIID, aInstancePtr); +} diff --git a/xpcom/base/nsMessageLoop.h b/xpcom/base/nsMessageLoop.h new file mode 100644 index 000000000..e7b146221 --- /dev/null +++ b/xpcom/base/nsMessageLoop.h @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIMessageLoop.h" + +/* + * nsMessageLoop implements nsIMessageLoop, which wraps Chromium's MessageLoop + * class and adds a bit of sugar. + */ +class nsMessageLoop : public nsIMessageLoop +{ + NS_DECL_ISUPPORTS + NS_DECL_NSIMESSAGELOOP + +private: + virtual ~nsMessageLoop() + { + } +}; + +#define NS_MESSAGE_LOOP_CID \ +{0x67b3ac0c, 0xd806, 0x4d48, \ +{0x93, 0x9e, 0x6a, 0x81, 0x9e, 0x6c, 0x24, 0x8f}} + +extern nsresult +nsMessageLoopConstructor(nsISupports* aOuter, + const nsIID& aIID, + void** aInstancePtr); diff --git a/xpcom/base/nsObjCExceptions.h b/xpcom/base/nsObjCExceptions.h new file mode 100644 index 000000000..e63c92af5 --- /dev/null +++ b/xpcom/base/nsObjCExceptions.h @@ -0,0 +1,230 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Undo the damage that exception_defines.h does. +#undef try +#undef catch + +#ifndef nsObjCExceptions_h_ +#define nsObjCExceptions_h_ + +#import <Foundation/Foundation.h> + +#ifdef DEBUG +#import <ExceptionHandling/NSExceptionHandler.h> +#endif + +#if defined(MOZ_CRASHREPORTER) && defined(__cplusplus) +#include "nsICrashReporter.h" +#include "nsCOMPtr.h" +#include "nsServiceManagerUtils.h" +#endif + +#include <unistd.h> +#include <signal.h> +#include "nsError.h" + +// Undo the damage that exception_defines.h does. +#undef try +#undef catch + +/* NOTE: Macros that claim to abort no longer abort, see bug 486574. + * If you actually want to log and abort, call "nsObjCExceptionLogAbort" + * from an exception handler. At some point we will fix this by replacing + * all macros in the tree with appropriately-named macros. + */ + +// See Mozilla bug 163260. +// This file can only be included in an Objective-C context. + +__attribute__((unused)) +static void +nsObjCExceptionLog(NSException* aException) +{ + NSLog(@"Mozilla has caught an Obj-C exception [%@: %@]", + [aException name], [aException reason]); + +#if defined(MOZ_CRASHREPORTER) && defined(__cplusplus) + // Attach exception info to the crash report. + nsCOMPtr<nsICrashReporter> crashReporter = + do_GetService("@mozilla.org/toolkit/crash-reporter;1"); + if (crashReporter) { + crashReporter->AppendObjCExceptionInfoToAppNotes(static_cast<void*>(aException)); + } +#endif + +#ifdef DEBUG + @try { + // Try to get stack information out of the exception. 10.5 returns the stack + // info with the callStackReturnAddresses selector. + NSArray* stackTrace = nil; + if ([aException respondsToSelector:@selector(callStackReturnAddresses)]) { + NSArray* addresses = (NSArray*) + [aException performSelector:@selector(callStackReturnAddresses)]; + if ([addresses count]) { + stackTrace = addresses; + } + } + + // 10.4 doesn't respond to callStackReturnAddresses so we'll try to pull the + // stack info out of the userInfo. It might not be there, sadly :( + if (!stackTrace) { + stackTrace = [[aException userInfo] objectForKey:NSStackTraceKey]; + } + + if (stackTrace) { + // The command line should look like this: + // /usr/bin/atos -p <pid> -printHeader <stack frame addresses> + NSMutableArray* args = + [NSMutableArray arrayWithCapacity:[stackTrace count] + 3]; + + [args addObject:@"-p"]; + int pid = [[NSProcessInfo processInfo] processIdentifier]; + [args addObject:[NSString stringWithFormat:@"%d", pid]]; + + [args addObject:@"-printHeader"]; + + unsigned int stackCount = [stackTrace count]; + unsigned int stackIndex = 0; + for (; stackIndex < stackCount; stackIndex++) { + unsigned long address = + [[stackTrace objectAtIndex:stackIndex] unsignedLongValue]; + [args addObject:[NSString stringWithFormat:@"0x%lx", address]]; + } + + NSPipe* outPipe = [NSPipe pipe]; + + NSTask* task = [[NSTask alloc] init]; + [task setLaunchPath:@"/usr/bin/atos"]; + [task setArguments:args]; + [task setStandardOutput:outPipe]; + [task setStandardError:outPipe]; + + NSLog(@"Generating stack trace for Obj-C exception..."); + + // This will throw an exception if the atos tool cannot be found, and in + // that case we'll just hit our @catch block below. + [task launch]; + + [task waitUntilExit]; + [task release]; + + NSData* outData = + [[outPipe fileHandleForReading] readDataToEndOfFile]; + NSString* outString = + [[NSString alloc] initWithData:outData encoding:NSUTF8StringEncoding]; + + NSLog(@"Stack trace:\n%@", outString); + + [outString release]; + } else { + NSLog(@"<No stack information available for Obj-C exception>"); + } + } + @catch (NSException* exn) { + NSLog(@"Failed to generate stack trace for Obj-C exception [%@: %@]", + [exn name], [exn reason]); + } +#endif +} + +__attribute__((unused)) +static void +nsObjCExceptionAbort() +{ + // We need to raise a mach-o signal here, the Mozilla crash reporter on + // Mac OS X does not respond to POSIX signals. Raising mach-o signals directly + // is tricky so we do it by just derefing a null pointer. + int* foo = nullptr; + *foo = 1; +} + +__attribute__((unused)) +static void +nsObjCExceptionLogAbort(NSException* aException) +{ + nsObjCExceptionLog(aException); + nsObjCExceptionAbort(); +} + +#define NS_OBJC_TRY(_e, _fail) \ +@try { _e; } \ +@catch(NSException *_exn) { \ + nsObjCExceptionLog(_exn); \ + _fail; \ +} + +#define NS_OBJC_TRY_EXPR(_e, _fail) \ +({ \ + typeof(_e) _tmp; \ + @try { _tmp = (_e); } \ + @catch(NSException *_exn) { \ + nsObjCExceptionLog(_exn); \ + _fail; \ + } \ + _tmp; \ +}) + +#define NS_OBJC_TRY_EXPR_NULL(_e) \ +NS_OBJC_TRY_EXPR(_e, 0) + +#define NS_OBJC_TRY_IGNORE(_e) \ +NS_OBJC_TRY(_e, ) + +// To reduce code size the abort versions do not reuse above macros. This allows +// catch blocks to only contain one call. + +#define NS_OBJC_TRY_ABORT(_e) \ +@try { _e; } \ +@catch(NSException *_exn) { \ + nsObjCExceptionLog(_exn); \ +} + +#define NS_OBJC_TRY_EXPR_ABORT(_e) \ +({ \ + typeof(_e) _tmp; \ + @try { _tmp = (_e); } \ + @catch(NSException *_exn) { \ + nsObjCExceptionLog(_exn); \ + } \ + _tmp; \ +}) + +// For wrapping blocks of Obj-C calls. Does not actually terminate. +#define NS_OBJC_BEGIN_TRY_ABORT_BLOCK @try { +#define NS_OBJC_END_TRY_ABORT_BLOCK } @catch(NSException *_exn) { \ + nsObjCExceptionLog(_exn); \ + } + +// Same as above ABORT_BLOCK but returns a value after the try/catch block to +// suppress compiler warnings. This allows us to avoid having to refactor code +// to get scoping right when wrapping an entire method. + +#define NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL @try { +#define NS_OBJC_END_TRY_ABORT_BLOCK_NIL } @catch(NSException *_exn) { \ + nsObjCExceptionLog(_exn); \ + } \ + return nil; + +#define NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSNULL @try { +#define NS_OBJC_END_TRY_ABORT_BLOCK_NSNULL } @catch(NSException *_exn) { \ + nsObjCExceptionLog(_exn); \ + } \ + return nullptr; + +#define NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT @try { +#define NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT } @catch(NSException *_exn) { \ + nsObjCExceptionLog(_exn); \ + } \ + return NS_ERROR_FAILURE; + +#define NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN @try { +#define NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(_rv) } @catch(NSException *_exn) { \ + nsObjCExceptionLog(_exn);\ + } \ + return _rv; + +#endif // nsObjCExceptions_h_ diff --git a/xpcom/base/nsQueryObject.h b/xpcom/base/nsQueryObject.h new file mode 100644 index 000000000..a52546324 --- /dev/null +++ b/xpcom/base/nsQueryObject.h @@ -0,0 +1,109 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsQueryObject_h +#define nsQueryObject_h + +#include "mozilla/Attributes.h" + +#include "nsCOMPtr.h" +#include "mozilla/RefPtr.h" + +/*****************************************************************************/ + +template<class T> +class MOZ_STACK_CLASS nsQueryObject final : public nsCOMPtr_helper +{ +public: + explicit nsQueryObject(T* aRawPtr) + : mRawPtr(aRawPtr) + { + } + + virtual nsresult NS_FASTCALL operator()(const nsIID& aIID, + void** aResult) const + { + nsresult status = mRawPtr ? mRawPtr->QueryInterface(aIID, aResult) + : NS_ERROR_NULL_POINTER; + return status; + } +private: + T* MOZ_NON_OWNING_REF mRawPtr; +}; + +template<class T> +class MOZ_STACK_CLASS nsQueryObjectWithError final : public nsCOMPtr_helper +{ +public: + nsQueryObjectWithError(T* aRawPtr, nsresult* aErrorPtr) + : mRawPtr(aRawPtr), mErrorPtr(aErrorPtr) + { + } + + virtual nsresult NS_FASTCALL operator()(const nsIID& aIID, + void** aResult) const + { + nsresult status = mRawPtr ? mRawPtr->QueryInterface(aIID, aResult) + : NS_ERROR_NULL_POINTER; + if (mErrorPtr) { + *mErrorPtr = status; + } + return status; + } +private: + T* MOZ_NON_OWNING_REF mRawPtr; + nsresult* mErrorPtr; +}; + +/*****************************************************************************/ + +/*****************************************************************************/ + +template<class T> +inline nsQueryObject<T> +do_QueryObject(T* aRawPtr) +{ + return nsQueryObject<T>(aRawPtr); +} + +template<class T> +inline nsQueryObject<T> +do_QueryObject(nsCOMPtr<T>& aRawPtr) +{ + return nsQueryObject<T>(aRawPtr); +} + +template<class T> +inline nsQueryObject<T> +do_QueryObject(RefPtr<T>& aRawPtr) +{ + return nsQueryObject<T>(aRawPtr); +} + +template<class T> +inline nsQueryObjectWithError<T> +do_QueryObject(T* aRawPtr, nsresult* aErrorPtr) +{ + return nsQueryObjectWithError<T>(aRawPtr, aErrorPtr); +} + +template<class T> +inline nsQueryObjectWithError<T> +do_QueryObject(nsCOMPtr<T>& aRawPtr, nsresult* aErrorPtr) +{ + return nsQueryObjectWithError<T>(aRawPtr, aErrorPtr); +} + +template<class T> +inline nsQueryObjectWithError<T> +do_QueryObject(RefPtr<T>& aRawPtr, nsresult* aErrorPtr) +{ + return nsQueryObjectWithError<T>(aRawPtr, aErrorPtr); +} + +/*****************************************************************************/ + +#endif // !defined(nsQueryObject_h) diff --git a/xpcom/base/nsSecurityConsoleMessage.cpp b/xpcom/base/nsSecurityConsoleMessage.cpp new file mode 100644 index 000000000..3e6409cf3 --- /dev/null +++ b/xpcom/base/nsSecurityConsoleMessage.cpp @@ -0,0 +1,45 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsSecurityConsoleMessage.h" + +NS_IMPL_ISUPPORTS(nsSecurityConsoleMessage, nsISecurityConsoleMessage) + +nsSecurityConsoleMessage::nsSecurityConsoleMessage() +{ +} + +nsSecurityConsoleMessage::~nsSecurityConsoleMessage() +{ +} + +NS_IMETHODIMP +nsSecurityConsoleMessage::GetTag(nsAString& aTag) +{ + aTag = mTag; + return NS_OK; +} + +NS_IMETHODIMP +nsSecurityConsoleMessage::SetTag(const nsAString& aTag) +{ + mTag = aTag; + return NS_OK; +} + +NS_IMETHODIMP +nsSecurityConsoleMessage::GetCategory(nsAString& aCategory) +{ + aCategory = mCategory; + return NS_OK; +} + +NS_IMETHODIMP +nsSecurityConsoleMessage::SetCategory(const nsAString& aCategory) +{ + mCategory = aCategory; + return NS_OK; +} diff --git a/xpcom/base/nsSecurityConsoleMessage.h b/xpcom/base/nsSecurityConsoleMessage.h new file mode 100644 index 000000000..c93c13613 --- /dev/null +++ b/xpcom/base/nsSecurityConsoleMessage.h @@ -0,0 +1,30 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsSecurityConsoleMessage_h__ +#define nsSecurityConsoleMessage_h__ +#include "nsISecurityConsoleMessage.h" +#include "nsString.h" + +class nsSecurityConsoleMessage final : public nsISecurityConsoleMessage +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSISECURITYCONSOLEMESSAGE + + nsSecurityConsoleMessage(); + +private: + ~nsSecurityConsoleMessage(); + +protected: + nsString mTag; + nsString mCategory; +}; + +#define NS_SECURITY_CONSOLE_MESSAGE_CID \ + {0x43ebf210, 0x8a7b, 0x4ddb, {0xa8, 0x3d, 0xb8, 0x7c, 0x51, 0xa0, 0x58, 0xdb}} +#endif //nsSecurityConsoleMessage_h__ diff --git a/xpcom/base/nsSetDllDirectory.h b/xpcom/base/nsSetDllDirectory.h new file mode 100644 index 000000000..0920d5936 --- /dev/null +++ b/xpcom/base/nsSetDllDirectory.h @@ -0,0 +1,45 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsSetDllDirectory_h +#define nsSetDllDirectory_h + +#ifndef XP_WIN +#error This file only makes sense on Windows. +#endif + +#include <windows.h> +#include <nscore.h> +#include <stdlib.h> + +namespace mozilla { + +static void +SanitizeEnvironmentVariables() +{ + DWORD bufferSize = GetEnvironmentVariableW(L"PATH", nullptr, 0); + if (bufferSize) { + wchar_t* originalPath = new wchar_t[bufferSize]; + if (bufferSize - 1 == GetEnvironmentVariableW(L"PATH", originalPath, + bufferSize)) { + bufferSize = ExpandEnvironmentStringsW(originalPath, nullptr, 0); + if (bufferSize) { + wchar_t* newPath = new wchar_t[bufferSize]; + if (ExpandEnvironmentStringsW(originalPath, + newPath, + bufferSize)) { + SetEnvironmentVariableW(L"PATH", newPath); + } + delete[] newPath; + } + } + delete[] originalPath; + } +} + +} + +#endif diff --git a/xpcom/base/nsStatusReporterManager.cpp b/xpcom/base/nsStatusReporterManager.cpp new file mode 100644 index 000000000..491e7d458 --- /dev/null +++ b/xpcom/base/nsStatusReporterManager.cpp @@ -0,0 +1,320 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsStatusReporterManager.h" +#include "nsCOMPtr.h" +#include "nsDirectoryServiceDefs.h" +#include "nsArrayEnumerator.h" +#include "nsISimpleEnumerator.h" +#include "nsIFile.h" +#include "nsDumpUtils.h" +#include "nsIFileStreams.h" +#include "nsPrintfCString.h" + +#ifdef XP_WIN +#include <process.h> +#define getpid _getpid +#else +#include <unistd.h> +#endif + +#ifdef XP_UNIX +#define DO_STATUS_REPORT 1 +#endif + +#ifdef DO_STATUS_REPORT // { +namespace { + +class DumpStatusInfoToTempDirRunnable : public mozilla::Runnable +{ +public: + DumpStatusInfoToTempDirRunnable() + { + } + + NS_IMETHOD Run() override + { + nsCOMPtr<nsIStatusReporterManager> mgr = + do_GetService("@mozilla.org/status-reporter-manager;1"); + mgr->DumpReports(); + return NS_OK; + } +}; + +void +doStatusReport(const nsCString& aInputStr) +{ + LOG("FifoWatcher(%s) dispatching status report runnable.", aInputStr.get()); + RefPtr<DumpStatusInfoToTempDirRunnable> runnable = + new DumpStatusInfoToTempDirRunnable(); + NS_DispatchToMainThread(runnable); +} + +} //anonymous namespace +#endif // DO_STATUS_REPORT } + +static bool gStatusReportProgress = 0; +static int gNumReporters = 0; + +nsresult +getStatus(nsACString& aDesc) +{ + if (!gStatusReportProgress) { + aDesc.AssignLiteral("Init"); + } else { + aDesc.AssignLiteral("Running: There are "); + aDesc.AppendInt(gNumReporters); + aDesc.AppendLiteral(" reporters"); + } + return NS_OK; +} + +NS_STATUS_REPORTER_IMPLEMENT(StatusReporter, "StatusReporter State", getStatus) + +#define DUMP(o, s) \ + do { \ + const char* s2 = (s); \ + uint32_t dummy; \ + nsresult rvDump = (o)->Write((s2), strlen(s2), &dummy); \ + if (NS_WARN_IF(NS_FAILED(rvDump))) \ + return rvDump; \ + } while (0) + +static nsresult +DumpReport(nsIFileOutputStream* aOStream, const nsCString& aProcess, + const nsCString& aName, const nsCString& aDescription) +{ + if (aProcess.IsEmpty()) { + int pid = getpid(); + nsPrintfCString pidStr("PID %u", pid); + DUMP(aOStream, "\n {\n \"Process\": \""); + DUMP(aOStream, pidStr.get()); + } else { + DUMP(aOStream, "\n { \"Unknown Process\": \""); + } + + DUMP(aOStream, "\",\n \"Reporter name\": \""); + DUMP(aOStream, aName.get()); + + DUMP(aOStream, "\",\n \"Status Description\": [\""); + nsCString desc = aDescription; + desc.ReplaceSubstring("|", "\",\""); + DUMP(aOStream, desc.get()); + + DUMP(aOStream, "\"]\n }"); + + return NS_OK; +} + +/** + ** nsStatusReporterManager implementation + **/ + +NS_IMPL_ISUPPORTS(nsStatusReporterManager, nsIStatusReporterManager) + +nsStatusReporterManager::nsStatusReporterManager() +{ +} + +nsStatusReporterManager::~nsStatusReporterManager() +{ +} + +NS_IMETHODIMP +nsStatusReporterManager::Init() +{ + RegisterReporter(new NS_STATUS_REPORTER_NAME(StatusReporter)); + gStatusReportProgress = 1; + +#ifdef DO_STATUS_REPORT + if (FifoWatcher::MaybeCreate()) { + FifoWatcher* fw = FifoWatcher::GetSingleton(); + fw->RegisterCallback(NS_LITERAL_CSTRING("status report"), doStatusReport); + } +#endif + + return NS_OK; +} + +NS_IMETHODIMP +nsStatusReporterManager::DumpReports() +{ + static unsigned number = 1; + nsresult rv; + + nsCString filename("status-reports-"); + filename.AppendInt(getpid()); + filename.Append('-'); + filename.AppendInt(number++); + filename.AppendLiteral(".json"); + + // Open a file in NS_OS_TEMP_DIR for writing. + // The file is initialized as "incomplete-status-reports-pid-number.json" in the + // begining, it will be rename as "status-reports-pid-number.json" in the end. + nsCOMPtr<nsIFile> tmpFile; + rv = nsDumpUtils::OpenTempFile(NS_LITERAL_CSTRING("incomplete-") + + filename, + getter_AddRefs(tmpFile), + NS_LITERAL_CSTRING("status-reports")); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsIFileOutputStream> ostream = + do_CreateInstance("@mozilla.org/network/file-output-stream;1"); + rv = ostream->Init(tmpFile, -1, -1, 0); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + //Write the reports to the file + + DUMP(ostream, "{\n\"subject\":\"about:service reports\",\n"); + DUMP(ostream, "\"reporters\": [ "); + + nsCOMPtr<nsISimpleEnumerator> e; + bool more, first = true; + EnumerateReporters(getter_AddRefs(e)); + while (NS_SUCCEEDED(e->HasMoreElements(&more)) && more) { + nsCOMPtr<nsISupports> supports; + e->GetNext(getter_AddRefs(supports)); + nsCOMPtr<nsIStatusReporter> r = do_QueryInterface(supports); + + nsCString process; + rv = r->GetProcess(process); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCString name; + rv = r->GetName(name); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCString description; + rv = r->GetDescription(description); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (first) { + first = false; + } else { + DUMP(ostream, ","); + } + + rv = DumpReport(ostream, process, name, description); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + DUMP(ostream, "\n]\n}\n"); + + rv = ostream->Close(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Rename the status reports file + nsCOMPtr<nsIFile> srFinalFile; + rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(srFinalFile)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + +#ifdef ANDROID + rv = srFinalFile->AppendNative(NS_LITERAL_CSTRING("status-reports")); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } +#endif + + rv = srFinalFile->AppendNative(filename); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = srFinalFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsAutoString srActualFinalFilename; + rv = srFinalFile->GetLeafName(srActualFinalFilename); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = tmpFile->MoveTo(/* directory */ nullptr, srActualFinalFilename); + + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsStatusReporterManager::EnumerateReporters(nsISimpleEnumerator** aResult) +{ + return NS_NewArrayEnumerator(aResult, mReporters); +} + +NS_IMETHODIMP +nsStatusReporterManager::RegisterReporter(nsIStatusReporter* aReporter) +{ + if (mReporters.IndexOf(aReporter) != -1) { + return NS_ERROR_FAILURE; + } + + mReporters.AppendObject(aReporter); + gNumReporters++; + return NS_OK; +} + +NS_IMETHODIMP +nsStatusReporterManager::UnregisterReporter(nsIStatusReporter* aReporter) +{ + if (!mReporters.RemoveObject(aReporter)) { + return NS_ERROR_FAILURE; + } + gNumReporters--; + return NS_OK; +} + +nsresult +NS_RegisterStatusReporter(nsIStatusReporter* aReporter) +{ + nsCOMPtr<nsIStatusReporterManager> mgr = + do_GetService("@mozilla.org/status-reporter-manager;1"); + if (!mgr) { + return NS_ERROR_FAILURE; + } + return mgr->RegisterReporter(aReporter); +} + +nsresult +NS_UnregisterStatusReporter(nsIStatusReporter* aReporter) +{ + nsCOMPtr<nsIStatusReporterManager> mgr = + do_GetService("@mozilla.org/status-reporter-manager;1"); + if (!mgr) { + return NS_ERROR_FAILURE; + } + return mgr->UnregisterReporter(aReporter); +} + +nsresult +NS_DumpStatusReporter() +{ + nsCOMPtr<nsIStatusReporterManager> mgr = + do_GetService("@mozilla.org/status-reporter-manager;1"); + if (!mgr) { + return NS_ERROR_FAILURE; + } + return mgr->DumpReports(); +} diff --git a/xpcom/base/nsStatusReporterManager.h b/xpcom/base/nsStatusReporterManager.h new file mode 100644 index 000000000..84427cfcf --- /dev/null +++ b/xpcom/base/nsStatusReporterManager.h @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + +#include "nsIStatusReporter.h" +#include "nsCOMArray.h" +#include "nsString.h" + +class nsStatusReporter final : public nsIStatusReporter +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSISTATUSREPORTER + + nsStatusReporter(nsACString& aProcess, nsACString& aDesc); + +private: + nsCString sProcess; + nsCString sName; + nsCString sDesc; + + virtual ~nsStatusReporter(); +}; + + +class nsStatusReporterManager : public nsIStatusReporterManager +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSISTATUSREPORTERMANAGER + + nsStatusReporterManager(); + +private: + nsCOMArray<nsIStatusReporter> mReporters; + + virtual ~nsStatusReporterManager(); +}; diff --git a/xpcom/base/nsSystemInfo.cpp b/xpcom/base/nsSystemInfo.cpp new file mode 100644 index 000000000..f6d9fd5ad --- /dev/null +++ b/xpcom/base/nsSystemInfo.cpp @@ -0,0 +1,985 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/ArrayUtils.h" + +#include "nsSystemInfo.h" +#include "prsystem.h" +#include "prio.h" +#include "prprf.h" +#include "mozilla/SSE.h" +#include "mozilla/arm.h" +#include "mozilla/Sprintf.h" + +#ifdef XP_WIN +#include <time.h> +#include <windows.h> +#include <winioctl.h> +#include "base/scoped_handle_win.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" +#include "nsIObserverService.h" +#include "nsWindowsHelpers.h" +#endif + +#ifdef XP_MACOSX +#include "MacHelpers.h" +#endif + +#ifdef MOZ_WIDGET_GTK +#include <gtk/gtk.h> +#include <dlfcn.h> +#endif + +#if defined (XP_LINUX) && !defined (ANDROID) +#include <unistd.h> +#include <fstream> +#include "mozilla/Tokenizer.h" +#include "nsCharSeparatedTokenizer.h" + +#include <map> +#include <string> +#endif + +#ifdef MOZ_WIDGET_ANDROID +#include "AndroidBridge.h" +#include "mozilla/dom/ContentChild.h" +#endif + +#ifdef MOZ_WIDGET_GONK +#include <sys/system_properties.h> +#include "mozilla/Preferences.h" +#include "nsPrintfCString.h" +#endif + +#ifdef ANDROID +extern "C" { +NS_EXPORT int android_sdk_version; +} +#endif + +#ifdef XP_MACOSX +#include <sys/sysctl.h> +#endif + +#if defined(XP_LINUX) && defined(MOZ_SANDBOX) +#include "mozilla/SandboxInfo.h" +#endif + +// Slot for NS_InitXPCOM2 to pass information to nsSystemInfo::Init. +// Only set to nonzero (potentially) if XP_UNIX. On such systems, the +// system call to discover the appropriate value is not thread-safe, +// so we must call it before going multithreaded, but nsSystemInfo::Init +// only happens well after that point. +uint32_t nsSystemInfo::gUserUmask = 0; + +#if defined (XP_LINUX) && !defined (ANDROID) +static void +SimpleParseKeyValuePairs(const std::string& aFilename, + std::map<nsCString, nsCString>& aKeyValuePairs) +{ + std::ifstream input(aFilename.c_str()); + for (std::string line; std::getline(input, line); ) { + nsAutoCString key, value; + + nsCCharSeparatedTokenizer tokens(nsDependentCString(line.c_str()), ':'); + if (tokens.hasMoreTokens()) { + key = tokens.nextToken(); + if (tokens.hasMoreTokens()) { + value = tokens.nextToken(); + } + // We want the value even if there was just one token, to cover the + // case where we had the key, and the value was blank (seems to be + // a valid scenario some files.) + aKeyValuePairs[key] = value; + } + } +} +#endif + +#if defined(XP_WIN) +namespace { +nsresult +GetHDDInfo(const char* aSpecialDirName, nsAutoCString& aModel, + nsAutoCString& aRevision) +{ + aModel.Truncate(); + aRevision.Truncate(); + + nsCOMPtr<nsIFile> profDir; + nsresult rv = NS_GetSpecialDirectory(aSpecialDirName, + getter_AddRefs(profDir)); + NS_ENSURE_SUCCESS(rv, rv); + nsAutoString profDirPath; + rv = profDir->GetPath(profDirPath); + NS_ENSURE_SUCCESS(rv, rv); + wchar_t volumeMountPoint[MAX_PATH] = {L'\\', L'\\', L'.', L'\\'}; + const size_t PREFIX_LEN = 4; + if (!::GetVolumePathNameW(profDirPath.get(), volumeMountPoint + PREFIX_LEN, + mozilla::ArrayLength(volumeMountPoint) - + PREFIX_LEN)) { + return NS_ERROR_UNEXPECTED; + } + size_t volumeMountPointLen = wcslen(volumeMountPoint); + // Since we would like to open a drive and not a directory, we need to + // remove any trailing backslash. A drive handle is valid for + // DeviceIoControl calls, a directory handle is not. + if (volumeMountPoint[volumeMountPointLen - 1] == L'\\') { + volumeMountPoint[volumeMountPointLen - 1] = L'\0'; + } + ScopedHandle handle(::CreateFileW(volumeMountPoint, 0, + FILE_SHARE_READ | FILE_SHARE_WRITE, + nullptr, OPEN_EXISTING, 0, nullptr)); + if (!handle.IsValid()) { + return NS_ERROR_UNEXPECTED; + } + STORAGE_PROPERTY_QUERY queryParameters = { + StorageDeviceProperty, PropertyStandardQuery + }; + STORAGE_DEVICE_DESCRIPTOR outputHeader = {sizeof(STORAGE_DEVICE_DESCRIPTOR)}; + DWORD bytesRead = 0; + if (!::DeviceIoControl(handle, IOCTL_STORAGE_QUERY_PROPERTY, + &queryParameters, sizeof(queryParameters), + &outputHeader, sizeof(outputHeader), &bytesRead, + nullptr)) { + return NS_ERROR_FAILURE; + } + PSTORAGE_DEVICE_DESCRIPTOR deviceOutput = + (PSTORAGE_DEVICE_DESCRIPTOR)malloc(outputHeader.Size); + if (!::DeviceIoControl(handle, IOCTL_STORAGE_QUERY_PROPERTY, + &queryParameters, sizeof(queryParameters), + deviceOutput, outputHeader.Size, &bytesRead, + nullptr)) { + free(deviceOutput); + return NS_ERROR_FAILURE; + } + // Some HDDs are including product ID info in the vendor field. Since PNP + // IDs include vendor info and product ID concatenated together, we'll do + // that here and interpret the result as a unique ID for the HDD model. + if (deviceOutput->VendorIdOffset) { + aModel = reinterpret_cast<char*>(deviceOutput) + + deviceOutput->VendorIdOffset; + } + if (deviceOutput->ProductIdOffset) { + aModel += reinterpret_cast<char*>(deviceOutput) + + deviceOutput->ProductIdOffset; + } + aModel.CompressWhitespace(); + if (deviceOutput->ProductRevisionOffset) { + aRevision = reinterpret_cast<char*>(deviceOutput) + + deviceOutput->ProductRevisionOffset; + aRevision.CompressWhitespace(); + } + free(deviceOutput); + return NS_OK; +} + +nsresult GetInstallYear(uint32_t& aYear) +{ + HKEY hKey; + LONG status = RegOpenKeyExW(HKEY_LOCAL_MACHINE, + NS_LITERAL_STRING( + "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion" + ).get(), + 0, KEY_READ | KEY_WOW64_64KEY, &hKey); + + if (status != ERROR_SUCCESS) { + return NS_ERROR_UNEXPECTED; + } + + nsAutoRegKey key(hKey); + + DWORD type = 0; + time_t raw_time = 0; + DWORD time_size = sizeof(time_t); + + status = RegQueryValueExW(hKey, L"InstallDate", + nullptr, &type, (LPBYTE)&raw_time, &time_size); + + if (status != ERROR_SUCCESS) { + return NS_ERROR_UNEXPECTED; + } + + if (type != REG_DWORD) { + return NS_ERROR_UNEXPECTED; + } + + tm time; + if (localtime_s(&time, &raw_time) != 0) { + return NS_ERROR_UNEXPECTED; + } + + aYear = 1900UL + time.tm_year; + return NS_OK; +} + +nsresult GetCountryCode(nsAString& aCountryCode) +{ + GEOID geoid = GetUserGeoID(GEOCLASS_NATION); + if (geoid == GEOID_NOT_AVAILABLE) { + return NS_ERROR_NOT_AVAILABLE; + } + // Get required length + int numChars = GetGeoInfoW(geoid, GEO_ISO2, nullptr, 0, 0); + if (!numChars) { + return NS_ERROR_FAILURE; + } + // Now get the string for real + aCountryCode.SetLength(numChars); + numChars = GetGeoInfoW(geoid, GEO_ISO2, wwc(aCountryCode.BeginWriting()), + aCountryCode.Length(), 0); + if (!numChars) { + return NS_ERROR_FAILURE; + } + + // numChars includes null terminator + aCountryCode.Truncate(numChars - 1); + return NS_OK; +} + +} // namespace +#endif // defined(XP_WIN) + +using namespace mozilla; + +nsSystemInfo::nsSystemInfo() +{ +} + +nsSystemInfo::~nsSystemInfo() +{ +} + +// CPU-specific information. +static const struct PropItems +{ + const char* name; + bool (*propfun)(void); +} cpuPropItems[] = { + // x86-specific bits. + { "hasMMX", mozilla::supports_mmx }, + { "hasSSE", mozilla::supports_sse }, + { "hasSSE2", mozilla::supports_sse2 }, + { "hasSSE3", mozilla::supports_sse3 }, + { "hasSSSE3", mozilla::supports_ssse3 }, + { "hasSSE4A", mozilla::supports_sse4a }, + { "hasSSE4_1", mozilla::supports_sse4_1 }, + { "hasSSE4_2", mozilla::supports_sse4_2 }, + { "hasAVX", mozilla::supports_avx }, + { "hasAVX2", mozilla::supports_avx2 }, + // ARM-specific bits. + { "hasEDSP", mozilla::supports_edsp }, + { "hasARMv6", mozilla::supports_armv6 }, + { "hasARMv7", mozilla::supports_armv7 }, + { "hasNEON", mozilla::supports_neon } +}; + +#ifdef XP_WIN +// Lifted from media/webrtc/trunk/webrtc/base/systeminfo.cc, +// so keeping the _ instead of switching to camel case for now. +typedef BOOL (WINAPI *LPFN_GLPI)( + PSYSTEM_LOGICAL_PROCESSOR_INFORMATION, + PDWORD); +static void +GetProcessorInformation(int* physical_cpus, int* cache_size_L2, int* cache_size_L3) +{ + MOZ_ASSERT(physical_cpus && cache_size_L2 && cache_size_L3); + + *physical_cpus = 0; + *cache_size_L2 = 0; // This will be in kbytes + *cache_size_L3 = 0; // This will be in kbytes + + // GetLogicalProcessorInformation() is available on Windows XP SP3 and beyond. + LPFN_GLPI glpi = reinterpret_cast<LPFN_GLPI>(GetProcAddress( + GetModuleHandle(L"kernel32"), + "GetLogicalProcessorInformation")); + if (nullptr == glpi) { + return; + } + // Determine buffer size, allocate and get processor information. + // Size can change between calls (unlikely), so a loop is done. + SYSTEM_LOGICAL_PROCESSOR_INFORMATION info_buffer[32]; + SYSTEM_LOGICAL_PROCESSOR_INFORMATION* infos = &info_buffer[0]; + DWORD return_length = sizeof(info_buffer); + while (!glpi(infos, &return_length)) { + if (GetLastError() == ERROR_INSUFFICIENT_BUFFER && infos == &info_buffer[0]) { + infos = new SYSTEM_LOGICAL_PROCESSOR_INFORMATION[return_length / sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION)]; + } else { + return; + } + } + + for (size_t i = 0; + i < return_length / sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION); ++i) { + if (infos[i].Relationship == RelationProcessorCore) { + ++*physical_cpus; + } else if (infos[i].Relationship == RelationCache) { + // Only care about L2 and L3 cache + switch (infos[i].Cache.Level) { + case 2: + *cache_size_L2 = static_cast<int>(infos[i].Cache.Size/1024); + break; + case 3: + *cache_size_L3 = static_cast<int>(infos[i].Cache.Size/1024); + break; + default: + break; + } + } + } + if (infos != &info_buffer[0]) { + delete [] infos; + } + return; +} +#endif + +nsresult +nsSystemInfo::Init() +{ + nsresult rv; + + static const struct + { + PRSysInfo cmd; + const char* name; + } items[] = { + { PR_SI_SYSNAME, "name" }, + { PR_SI_HOSTNAME, "host" }, + { PR_SI_ARCHITECTURE, "arch" }, + { PR_SI_RELEASE, "version" } + }; + + for (uint32_t i = 0; i < (sizeof(items) / sizeof(items[0])); i++) { + char buf[SYS_INFO_BUFFER_LENGTH]; + if (PR_GetSystemInfo(items[i].cmd, buf, sizeof(buf)) == PR_SUCCESS) { + rv = SetPropertyAsACString(NS_ConvertASCIItoUTF16(items[i].name), + nsDependentCString(buf)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } else { + NS_WARNING("PR_GetSystemInfo failed"); + } + } + + rv = SetPropertyAsBool(NS_ConvertASCIItoUTF16("hasWindowsTouchInterface"), + false); + NS_ENSURE_SUCCESS(rv, rv); + + // Additional informations not available through PR_GetSystemInfo. + SetInt32Property(NS_LITERAL_STRING("pagesize"), PR_GetPageSize()); + SetInt32Property(NS_LITERAL_STRING("pageshift"), PR_GetPageShift()); + SetInt32Property(NS_LITERAL_STRING("memmapalign"), PR_GetMemMapAlignment()); + SetUint64Property(NS_LITERAL_STRING("memsize"), PR_GetPhysicalMemorySize()); + SetUint32Property(NS_LITERAL_STRING("umask"), nsSystemInfo::gUserUmask); + + uint64_t virtualMem = 0; + nsAutoCString cpuVendor; + int cpuSpeed = -1; + int cpuFamily = -1; + int cpuModel = -1; + int cpuStepping = -1; + int logicalCPUs = -1; + int physicalCPUs = -1; + int cacheSizeL2 = -1; + int cacheSizeL3 = -1; + +#if defined (XP_WIN) + // Virtual memory: + MEMORYSTATUSEX memStat; + memStat.dwLength = sizeof(memStat); + if (GlobalMemoryStatusEx(&memStat)) { + virtualMem = memStat.ullTotalVirtual; + } + + // CPU speed + HKEY key; + static const WCHAR keyName[] = + L"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0"; + + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, keyName , 0, KEY_QUERY_VALUE, &key) + == ERROR_SUCCESS) { + DWORD data, len, vtype; + len = sizeof(data); + + if (RegQueryValueEx(key, L"~Mhz", 0, 0, reinterpret_cast<LPBYTE>(&data), + &len) == ERROR_SUCCESS) { + cpuSpeed = static_cast<int>(data); + } + + // Limit to 64 double byte characters, should be plenty, but create + // a buffer one larger as the result may not be null terminated. If + // it is more than 64, we will not get the value. + wchar_t cpuVendorStr[64+1]; + len = sizeof(cpuVendorStr)-2; + if (RegQueryValueExW(key, L"VendorIdentifier", + 0, &vtype, + reinterpret_cast<LPBYTE>(cpuVendorStr), + &len) == ERROR_SUCCESS && + vtype == REG_SZ && len % 2 == 0 && len > 1) { + cpuVendorStr[len/2] = 0; // In case it isn't null terminated + CopyUTF16toUTF8(nsDependentString(cpuVendorStr), cpuVendor); + } + + RegCloseKey(key); + } + + // Other CPU attributes: + SYSTEM_INFO si; + GetNativeSystemInfo(&si); + logicalCPUs = si.dwNumberOfProcessors; + GetProcessorInformation(&physicalCPUs, &cacheSizeL2, &cacheSizeL3); + if (physicalCPUs <= 0) { + physicalCPUs = logicalCPUs; + } + cpuFamily = si.wProcessorLevel; + cpuModel = si.wProcessorRevision >> 8; + cpuStepping = si.wProcessorRevision & 0xFF; +#elif defined (XP_MACOSX) + // CPU speed + uint64_t sysctlValue64 = 0; + uint32_t sysctlValue32 = 0; + size_t len = 0; + len = sizeof(sysctlValue64); + if (!sysctlbyname("hw.cpufrequency_max", &sysctlValue64, &len, NULL, 0)) { + cpuSpeed = static_cast<int>(sysctlValue64/1000000); + } + MOZ_ASSERT(sizeof(sysctlValue64) == len); + + len = sizeof(sysctlValue32); + if (!sysctlbyname("hw.physicalcpu_max", &sysctlValue32, &len, NULL, 0)) { + physicalCPUs = static_cast<int>(sysctlValue32); + } + MOZ_ASSERT(sizeof(sysctlValue32) == len); + + len = sizeof(sysctlValue32); + if (!sysctlbyname("hw.logicalcpu_max", &sysctlValue32, &len, NULL, 0)) { + logicalCPUs = static_cast<int>(sysctlValue32); + } + MOZ_ASSERT(sizeof(sysctlValue32) == len); + + len = sizeof(sysctlValue64); + if (!sysctlbyname("hw.l2cachesize", &sysctlValue64, &len, NULL, 0)) { + cacheSizeL2 = static_cast<int>(sysctlValue64/1024); + } + MOZ_ASSERT(sizeof(sysctlValue64) == len); + + len = sizeof(sysctlValue64); + if (!sysctlbyname("hw.l3cachesize", &sysctlValue64, &len, NULL, 0)) { + cacheSizeL3 = static_cast<int>(sysctlValue64/1024); + } + MOZ_ASSERT(sizeof(sysctlValue64) == len); + + if (!sysctlbyname("machdep.cpu.vendor", NULL, &len, NULL, 0)) { + char* cpuVendorStr = new char[len]; + if (!sysctlbyname("machdep.cpu.vendor", cpuVendorStr, &len, NULL, 0)) { + cpuVendor = cpuVendorStr; + } + delete [] cpuVendorStr; + } + + len = sizeof(sysctlValue32); + if (!sysctlbyname("machdep.cpu.family", &sysctlValue32, &len, NULL, 0)) { + cpuFamily = static_cast<int>(sysctlValue32); + } + MOZ_ASSERT(sizeof(sysctlValue32) == len); + + len = sizeof(sysctlValue32); + if (!sysctlbyname("machdep.cpu.model", &sysctlValue32, &len, NULL, 0)) { + cpuModel = static_cast<int>(sysctlValue32); + } + MOZ_ASSERT(sizeof(sysctlValue32) == len); + + len = sizeof(sysctlValue32); + if (!sysctlbyname("machdep.cpu.stepping", &sysctlValue32, &len, NULL, 0)) { + cpuStepping = static_cast<int>(sysctlValue32); + } + MOZ_ASSERT(sizeof(sysctlValue32) == len); + +#elif defined (XP_LINUX) && !defined (ANDROID) + // Get vendor, family, model, stepping, physical cores, L3 cache size + // from /proc/cpuinfo file + { + std::map<nsCString, nsCString> keyValuePairs; + SimpleParseKeyValuePairs("/proc/cpuinfo", keyValuePairs); + + // cpuVendor from "vendor_id" + cpuVendor.Assign(keyValuePairs[NS_LITERAL_CSTRING("vendor_id")]); + + { + // cpuFamily from "cpu family" + Tokenizer::Token t; + Tokenizer p(keyValuePairs[NS_LITERAL_CSTRING("cpu family")]); + if (p.Next(t) && t.Type() == Tokenizer::TOKEN_INTEGER && + t.AsInteger() <= INT32_MAX) { + cpuFamily = static_cast<int>(t.AsInteger()); + } + } + + { + // cpuModel from "model" + Tokenizer::Token t; + Tokenizer p(keyValuePairs[NS_LITERAL_CSTRING("model")]); + if (p.Next(t) && t.Type() == Tokenizer::TOKEN_INTEGER && + t.AsInteger() <= INT32_MAX) { + cpuModel = static_cast<int>(t.AsInteger()); + } + } + + { + // cpuStepping from "stepping" + Tokenizer::Token t; + Tokenizer p(keyValuePairs[NS_LITERAL_CSTRING("stepping")]); + if (p.Next(t) && t.Type() == Tokenizer::TOKEN_INTEGER && + t.AsInteger() <= INT32_MAX) { + cpuStepping = static_cast<int>(t.AsInteger()); + } + } + + { + // physicalCPUs from "cpu cores" + Tokenizer::Token t; + Tokenizer p(keyValuePairs[NS_LITERAL_CSTRING("cpu cores")]); + if (p.Next(t) && t.Type() == Tokenizer::TOKEN_INTEGER && + t.AsInteger() <= INT32_MAX) { + physicalCPUs = static_cast<int>(t.AsInteger()); + } + } + + { + // cacheSizeL3 from "cache size" + Tokenizer::Token t; + Tokenizer p(keyValuePairs[NS_LITERAL_CSTRING("cache size")]); + if (p.Next(t) && t.Type() == Tokenizer::TOKEN_INTEGER && + t.AsInteger() <= INT32_MAX) { + cacheSizeL3 = static_cast<int>(t.AsInteger()); + if (p.Next(t) && t.Type() == Tokenizer::TOKEN_WORD && + t.AsString() != NS_LITERAL_CSTRING("KB")) { + // If we get here, there was some text after the cache size value + // and that text was not KB. For now, just don't report the + // L3 cache. + cacheSizeL3 = -1; + } + } + } + } + + { + // Get cpuSpeed from another file. + std::ifstream input("/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq"); + std::string line; + if (getline(input, line)) { + Tokenizer::Token t; + Tokenizer p(line.c_str()); + if (p.Next(t) && t.Type() == Tokenizer::TOKEN_INTEGER && + t.AsInteger() <= INT32_MAX) { + cpuSpeed = static_cast<int>(t.AsInteger()/1000); + } + } + } + + { + // Get cacheSizeL2 from yet another file + std::ifstream input("/sys/devices/system/cpu/cpu0/cache/index2/size"); + std::string line; + if (getline(input, line)) { + Tokenizer::Token t; + Tokenizer p(line.c_str(), nullptr, "K"); + if (p.Next(t) && t.Type() == Tokenizer::TOKEN_INTEGER && + t.AsInteger() <= INT32_MAX) { + cacheSizeL2 = static_cast<int>(t.AsInteger()); + } + } + } + + SetInt32Property(NS_LITERAL_STRING("cpucount"), PR_GetNumberOfProcessors()); +#else + SetInt32Property(NS_LITERAL_STRING("cpucount"), PR_GetNumberOfProcessors()); +#endif + + if (virtualMem) SetUint64Property(NS_LITERAL_STRING("virtualmemsize"), virtualMem); + if (cpuSpeed >= 0) SetInt32Property(NS_LITERAL_STRING("cpuspeed"), cpuSpeed); + if (!cpuVendor.IsEmpty()) SetPropertyAsACString(NS_LITERAL_STRING("cpuvendor"), cpuVendor); + if (cpuFamily >= 0) SetInt32Property(NS_LITERAL_STRING("cpufamily"), cpuFamily); + if (cpuModel >= 0) SetInt32Property(NS_LITERAL_STRING("cpumodel"), cpuModel); + if (cpuStepping >= 0) SetInt32Property(NS_LITERAL_STRING("cpustepping"), cpuStepping); + + if (logicalCPUs >= 0) SetInt32Property(NS_LITERAL_STRING("cpucount"), logicalCPUs); + if (physicalCPUs >= 0) SetInt32Property(NS_LITERAL_STRING("cpucores"), physicalCPUs); + + if (cacheSizeL2 >= 0) SetInt32Property(NS_LITERAL_STRING("cpucachel2"), cacheSizeL2); + if (cacheSizeL3 >= 0) SetInt32Property(NS_LITERAL_STRING("cpucachel3"), cacheSizeL3); + + for (uint32_t i = 0; i < ArrayLength(cpuPropItems); i++) { + rv = SetPropertyAsBool(NS_ConvertASCIItoUTF16(cpuPropItems[i].name), + cpuPropItems[i].propfun()); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + +#ifdef XP_WIN + BOOL isWow64; + BOOL gotWow64Value = IsWow64Process(GetCurrentProcess(), &isWow64); + NS_WARNING_ASSERTION(gotWow64Value, "IsWow64Process failed"); + if (gotWow64Value) { + rv = SetPropertyAsBool(NS_LITERAL_STRING("isWow64"), !!isWow64); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + if (NS_FAILED(GetProfileHDDInfo())) { + // We might have been called before profile-do-change. We'll observe that + // event so that we can fill this in later. + nsCOMPtr<nsIObserverService> obsService = + do_GetService(NS_OBSERVERSERVICE_CONTRACTID, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + rv = obsService->AddObserver(this, "profile-do-change", false); + if (NS_FAILED(rv)) { + return rv; + } + } + nsAutoCString hddModel, hddRevision; + if (NS_SUCCEEDED(GetHDDInfo(NS_GRE_DIR, hddModel, hddRevision))) { + rv = SetPropertyAsACString(NS_LITERAL_STRING("binHDDModel"), hddModel); + NS_ENSURE_SUCCESS(rv, rv); + rv = SetPropertyAsACString(NS_LITERAL_STRING("binHDDRevision"), + hddRevision); + NS_ENSURE_SUCCESS(rv, rv); + } + if (NS_SUCCEEDED(GetHDDInfo(NS_WIN_WINDOWS_DIR, hddModel, hddRevision))) { + rv = SetPropertyAsACString(NS_LITERAL_STRING("winHDDModel"), hddModel); + NS_ENSURE_SUCCESS(rv, rv); + rv = SetPropertyAsACString(NS_LITERAL_STRING("winHDDRevision"), + hddRevision); + NS_ENSURE_SUCCESS(rv, rv); + } + + nsAutoString countryCode; + if (NS_SUCCEEDED(GetCountryCode(countryCode))) { + rv = SetPropertyAsAString(NS_LITERAL_STRING("countryCode"), countryCode); + NS_ENSURE_SUCCESS(rv, rv); + } + + uint32_t installYear = 0; + if (NS_SUCCEEDED(GetInstallYear(installYear))) { + rv = SetPropertyAsUint32(NS_LITERAL_STRING("installYear"), installYear); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } +#endif + +#if defined(XP_MACOSX) + nsAutoString countryCode; + if (NS_SUCCEEDED(GetSelectedCityInfo(countryCode))) { + rv = SetPropertyAsAString(NS_LITERAL_STRING("countryCode"), countryCode); + NS_ENSURE_SUCCESS(rv, rv); + } +#endif + +#if defined(MOZ_WIDGET_GTK) + // This must be done here because NSPR can only separate OS's when compiled, not libraries. + // 64 bytes is going to be well enough for "GTK " followed by 3 integers + // separated with dots. + char gtkver[64]; + ssize_t gtkver_len = 0; + +#if MOZ_WIDGET_GTK == 2 + extern int gtk_read_end_of_the_pipe; + + if (gtk_read_end_of_the_pipe != -1) { + do { + gtkver_len = read(gtk_read_end_of_the_pipe, >kver, sizeof(gtkver)); + } while (gtkver_len < 0 && errno == EINTR); + close(gtk_read_end_of_the_pipe); + } +#endif + + if (gtkver_len <= 0) { + gtkver_len = SprintfLiteral(gtkver, "GTK %u.%u.%u", gtk_major_version, + gtk_minor_version, gtk_micro_version); + } + + nsAutoCString secondaryLibrary; + if (gtkver_len > 0 && gtkver_len < int(sizeof(gtkver))) { + secondaryLibrary.Append(nsDependentCSubstring(gtkver, gtkver_len)); + } + + void* libpulse = dlopen("libpulse.so.0", RTLD_LAZY); + const char* libpulseVersion = "not-available"; + if (libpulse) { + auto pa_get_library_version = reinterpret_cast<const char* (*)()> + (dlsym(libpulse, "pa_get_library_version")); + + if (pa_get_library_version) { + libpulseVersion = pa_get_library_version(); + } + } + + secondaryLibrary.AppendPrintf(",libpulse %s", libpulseVersion); + + if (libpulse) { + dlclose(libpulse); + } + + rv = SetPropertyAsACString(NS_LITERAL_STRING("secondaryLibrary"), + secondaryLibrary); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } +#endif + +#ifdef MOZ_WIDGET_ANDROID + AndroidSystemInfo info; + if (XRE_IsContentProcess()) { + dom::ContentChild* child = dom::ContentChild::GetSingleton(); + if (child) { + child->SendGetAndroidSystemInfo(&info); + SetupAndroidInfo(info); + } + } else { + GetAndroidSystemInfo(&info); + SetupAndroidInfo(info); + } +#endif + +#ifdef MOZ_WIDGET_GONK + char sdk[PROP_VALUE_MAX]; + if (__system_property_get("ro.build.version.sdk", sdk)) { + android_sdk_version = atoi(sdk); + SetPropertyAsInt32(NS_LITERAL_STRING("sdk_version"), android_sdk_version); + + SetPropertyAsACString(NS_LITERAL_STRING("secondaryLibrary"), + nsPrintfCString("SDK %u", android_sdk_version)); + } + + char characteristics[PROP_VALUE_MAX]; + if (__system_property_get("ro.build.characteristics", characteristics)) { + if (!strcmp(characteristics, "tablet")) { + SetPropertyAsBool(NS_LITERAL_STRING("tablet"), true); + } else if (!strcmp(characteristics, "tv")) { + SetPropertyAsBool(NS_LITERAL_STRING("tv"), true); + } + } + + nsAutoString str; + rv = GetPropertyAsAString(NS_LITERAL_STRING("version"), str); + if (NS_SUCCEEDED(rv)) { + SetPropertyAsAString(NS_LITERAL_STRING("kernel_version"), str); + } + + const nsAdoptingString& b2g_os_name = + mozilla::Preferences::GetString("b2g.osName"); + if (b2g_os_name) { + SetPropertyAsAString(NS_LITERAL_STRING("name"), b2g_os_name); + } + + const nsAdoptingString& b2g_version = + mozilla::Preferences::GetString("b2g.version"); + if (b2g_version) { + SetPropertyAsAString(NS_LITERAL_STRING("version"), b2g_version); + } +#endif + +#if defined(XP_LINUX) && defined(MOZ_SANDBOX) + SandboxInfo sandInfo = SandboxInfo::Get(); + + SetPropertyAsBool(NS_LITERAL_STRING("hasSeccompBPF"), + sandInfo.Test(SandboxInfo::kHasSeccompBPF)); + SetPropertyAsBool(NS_LITERAL_STRING("hasSeccompTSync"), + sandInfo.Test(SandboxInfo::kHasSeccompTSync)); + SetPropertyAsBool(NS_LITERAL_STRING("hasUserNamespaces"), + sandInfo.Test(SandboxInfo::kHasUserNamespaces)); + SetPropertyAsBool(NS_LITERAL_STRING("hasPrivilegedUserNamespaces"), + sandInfo.Test(SandboxInfo::kHasPrivilegedUserNamespaces)); + + if (sandInfo.Test(SandboxInfo::kEnabledForContent)) { + SetPropertyAsBool(NS_LITERAL_STRING("canSandboxContent"), + sandInfo.CanSandboxContent()); + } + + if (sandInfo.Test(SandboxInfo::kEnabledForMedia)) { + SetPropertyAsBool(NS_LITERAL_STRING("canSandboxMedia"), + sandInfo.CanSandboxMedia()); + } +#endif // XP_LINUX && MOZ_SANDBOX + + return NS_OK; +} + +#ifdef MOZ_WIDGET_ANDROID +// Prerelease versions of Android use a letter instead of version numbers. +// Unfortunately this breaks websites due to the user agent. +// Chrome works around this by hardcoding an Android version when a +// numeric version can't be obtained. We're doing the same. +// This version will need to be updated whenever there is a new official +// Android release. +// See: https://cs.chromium.org/chromium/src/base/sys_info_android.cc?l=61 +#define DEFAULT_ANDROID_VERSION "6.0.99" + +/* static */ +void +nsSystemInfo::GetAndroidSystemInfo(AndroidSystemInfo* aInfo) +{ + MOZ_ASSERT(XRE_IsParentProcess()); + + if (!mozilla::AndroidBridge::Bridge()) { + aInfo->sdk_version() = 0; + return; + } + + nsAutoString str; + if (mozilla::AndroidBridge::Bridge()->GetStaticStringField( + "android/os/Build", "MODEL", str)) { + aInfo->device() = str; + } + if (mozilla::AndroidBridge::Bridge()->GetStaticStringField( + "android/os/Build", "MANUFACTURER", str)) { + aInfo->manufacturer() = str; + } + if (mozilla::AndroidBridge::Bridge()->GetStaticStringField( + "android/os/Build$VERSION", "RELEASE", str)) { + int major_version; + int minor_version; + int bugfix_version; + int num_read = sscanf(NS_ConvertUTF16toUTF8(str).get(), "%d.%d.%d", &major_version, &minor_version, &bugfix_version); + if (num_read == 0) { + aInfo->release_version() = NS_LITERAL_STRING(DEFAULT_ANDROID_VERSION); + } else { + aInfo->release_version() = str; + } + } + if (mozilla::AndroidBridge::Bridge()->GetStaticStringField( + "android/os/Build", "HARDWARE", str)) { + aInfo->hardware() = str; + } + int32_t sdk_version; + if (!mozilla::AndroidBridge::Bridge()->GetStaticIntField( + "android/os/Build$VERSION", "SDK_INT", &sdk_version)) { + sdk_version = 0; + } + aInfo->sdk_version() = sdk_version; + aInfo->isTablet() = java::GeckoAppShell::IsTablet(); +} + +void +nsSystemInfo::SetupAndroidInfo(const AndroidSystemInfo& aInfo) +{ + if (!aInfo.device().IsEmpty()) { + SetPropertyAsAString(NS_LITERAL_STRING("device"), aInfo.device()); + } + if (!aInfo.manufacturer().IsEmpty()) { + SetPropertyAsAString(NS_LITERAL_STRING("manufacturer"), aInfo.manufacturer()); + } + if (!aInfo.release_version().IsEmpty()) { + SetPropertyAsAString(NS_LITERAL_STRING("release_version"), aInfo.release_version()); + } + SetPropertyAsBool(NS_LITERAL_STRING("tablet"), aInfo.isTablet()); + // NSPR "version" is the kernel version. For Android we want the Android version. + // Rename SDK version to version and put the kernel version into kernel_version. + nsAutoString str; + nsresult rv = GetPropertyAsAString(NS_LITERAL_STRING("version"), str); + if (NS_SUCCEEDED(rv)) { + SetPropertyAsAString(NS_LITERAL_STRING("kernel_version"), str); + } + // When AndroidBridge is not available (eg. in xpcshell tests), sdk_version is 0. + if (aInfo.sdk_version() != 0) { + android_sdk_version = aInfo.sdk_version(); + if (android_sdk_version >= 8 && !aInfo.hardware().IsEmpty()) { + SetPropertyAsAString(NS_LITERAL_STRING("hardware"), aInfo.hardware()); + } + SetPropertyAsInt32(NS_LITERAL_STRING("version"), android_sdk_version); + } +} +#endif // MOZ_WIDGET_ANDROID + +void +nsSystemInfo::SetInt32Property(const nsAString& aPropertyName, + const int32_t aValue) +{ + NS_WARNING_ASSERTION(aValue > 0, "Unable to read system value"); + if (aValue > 0) { +#ifdef DEBUG + nsresult rv = +#endif + SetPropertyAsInt32(aPropertyName, aValue); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Unable to set property"); + } +} + +void +nsSystemInfo::SetUint32Property(const nsAString& aPropertyName, + const uint32_t aValue) +{ + // Only one property is currently set via this function. + // It may legitimately be zero. +#ifdef DEBUG + nsresult rv = +#endif + SetPropertyAsUint32(aPropertyName, aValue); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Unable to set property"); +} + +void +nsSystemInfo::SetUint64Property(const nsAString& aPropertyName, + const uint64_t aValue) +{ + NS_WARNING_ASSERTION(aValue > 0, "Unable to read system value"); + if (aValue > 0) { +#ifdef DEBUG + nsresult rv = +#endif + SetPropertyAsUint64(aPropertyName, aValue); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Unable to set property"); + } +} + +#if defined(XP_WIN) +NS_IMETHODIMP +nsSystemInfo::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) +{ + if (!strcmp(aTopic, "profile-do-change")) { + nsresult rv; + nsCOMPtr<nsIObserverService> obsService = + do_GetService(NS_OBSERVERSERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + return rv; + } + rv = obsService->RemoveObserver(this, "profile-do-change"); + if (NS_FAILED(rv)) { + return rv; + } + return GetProfileHDDInfo(); + } + return NS_OK; +} + +nsresult +nsSystemInfo::GetProfileHDDInfo() +{ + nsAutoCString hddModel, hddRevision; + nsresult rv = GetHDDInfo(NS_APP_USER_PROFILE_50_DIR, hddModel, hddRevision); + if (NS_FAILED(rv)) { + return rv; + } + rv = SetPropertyAsACString(NS_LITERAL_STRING("profileHDDModel"), hddModel); + if (NS_FAILED(rv)) { + return rv; + } + rv = SetPropertyAsACString(NS_LITERAL_STRING("profileHDDRevision"), + hddRevision); + return rv; +} + +NS_IMPL_ISUPPORTS_INHERITED(nsSystemInfo, nsHashPropertyBag, nsIObserver) +#endif // defined(XP_WIN) + diff --git a/xpcom/base/nsSystemInfo.h b/xpcom/base/nsSystemInfo.h new file mode 100644 index 000000000..5a7ef4424 --- /dev/null +++ b/xpcom/base/nsSystemInfo.h @@ -0,0 +1,66 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _NSSYSTEMINFO_H_ +#define _NSSYSTEMINFO_H_ + +#include "nsHashPropertyBag.h" +#if defined(XP_WIN) +#include "nsIObserver.h" +#endif // defined(XP_WIN) + +#ifdef MOZ_WIDGET_ANDROID +#include "mozilla/dom/PContent.h" +#endif // MOZ_WIDGET_ANDROID + +class nsSystemInfo final + : public nsHashPropertyBag +#if defined(XP_WIN) + , public nsIObserver +#endif // defined(XP_WIN) +{ +public: +#if defined(XP_WIN) + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIOBSERVER +#endif // defined(XP_WIN) + + nsSystemInfo(); + + nsresult Init(); + + // Slot for NS_InitXPCOM2 to pass information to nsSystemInfo::Init. + // See comments above the variable definition and in NS_InitXPCOM2. + static uint32_t gUserUmask; + +#ifdef MOZ_WIDGET_ANDROID + static void GetAndroidSystemInfo(mozilla::dom::AndroidSystemInfo* aInfo); + protected: + void SetupAndroidInfo(const mozilla::dom::AndroidSystemInfo&); +#endif + +protected: + void SetInt32Property(const nsAString& aPropertyName, + const int32_t aValue); + void SetUint32Property(const nsAString& aPropertyName, + const uint32_t aValue); + void SetUint64Property(const nsAString& aPropertyName, + const uint64_t aValue); + +private: + ~nsSystemInfo(); + +#if defined(XP_WIN) + nsresult GetProfileHDDInfo(); +#endif // defined(XP_WIN) +}; + +#define NS_SYSTEMINFO_CONTRACTID "@mozilla.org/system-info;1" +#define NS_SYSTEMINFO_CID \ +{ 0xd962398a, 0x99e5, 0x49b2, \ +{ 0x85, 0x7a, 0xc1, 0x59, 0x04, 0x9c, 0x7f, 0x6c } } + +#endif /* _NSSYSTEMINFO_H_ */ diff --git a/xpcom/base/nsTraceRefcnt.cpp b/xpcom/base/nsTraceRefcnt.cpp new file mode 100644 index 000000000..3b415174b --- /dev/null +++ b/xpcom/base/nsTraceRefcnt.cpp @@ -0,0 +1,1319 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsTraceRefcnt.h" +#include "mozilla/IntegerPrintfMacros.h" +#include "mozilla/StaticPtr.h" +#include "nsXPCOMPrivate.h" +#include "nscore.h" +#include "nsISupports.h" +#include "nsTArray.h" +#include "nsTHashtable.h" +#include "prenv.h" +#include "plstr.h" +#include "prlink.h" +#include "nsCRT.h" +#include <math.h> +#include "nsHashKeys.h" +#include "mozilla/StackWalk.h" +#include "nsThreadUtils.h" +#include "CodeAddressService.h" + +#include "nsXULAppAPI.h" +#ifdef XP_WIN +#include <process.h> +#define getpid _getpid +#else +#include <unistd.h> +#endif + +#include "mozilla/Atomics.h" +#include "mozilla/AutoRestore.h" +#include "mozilla/BlockingResourceBase.h" +#include "mozilla/PoisonIOInterposer.h" + +#include <string> +#include <vector> + +#ifdef HAVE_DLOPEN +#include <dlfcn.h> +#endif + +#ifdef MOZ_DMD +#include "base/process_util.h" +#include "nsMemoryInfoDumper.h" +#endif + +//////////////////////////////////////////////////////////////////////////////// + +#include "plhash.h" +#include "prmem.h" + +#include "prthread.h" + +// We use a spin lock instead of a regular mutex because this lock is usually +// only held for a very short time, and gets grabbed at a very high frequency +// (~100000 times per second). On Mac, the overhead of using a regular lock +// is very high, see bug 1137963. +static mozilla::Atomic<uintptr_t, mozilla::ReleaseAcquire> gTraceLogLocked; + +struct MOZ_STACK_CLASS AutoTraceLogLock final +{ + bool doRelease; + AutoTraceLogLock() + : doRelease(true) + { + uintptr_t currentThread = reinterpret_cast<uintptr_t>(PR_GetCurrentThread()); + if (gTraceLogLocked == currentThread) { + doRelease = false; + } else { + while (!gTraceLogLocked.compareExchange(0, currentThread)) { + PR_Sleep(PR_INTERVAL_NO_WAIT); /* yield */ + } + } + } + ~AutoTraceLogLock() { if (doRelease) gTraceLogLocked = 0; } +}; + +static PLHashTable* gBloatView; +static PLHashTable* gTypesToLog; +static PLHashTable* gObjectsToLog; +static PLHashTable* gSerialNumbers; +static intptr_t gNextSerialNumber; +static bool gDumpedStatistics = false; + +// By default, debug builds only do bloat logging. Bloat logging +// only tries to record when an object is created or destroyed, so we +// optimize the common case in NS_LogAddRef and NS_LogRelease where +// only bloat logging is enabled and no logging needs to be done. +enum LoggingType +{ + NoLogging, + OnlyBloatLogging, + FullLogging +}; + +static LoggingType gLogging; + +static bool gLogLeaksOnly; + +#define BAD_TLS_INDEX ((unsigned)-1) + +// if gActivityTLS == BAD_TLS_INDEX, then we're +// unitialized... otherwise this points to a NSPR TLS thread index +// indicating whether addref activity is legal. If the PTR_TO_INT32 is 0 then +// activity is ok, otherwise not! +static unsigned gActivityTLS = BAD_TLS_INDEX; + +static bool gInitialized; +static nsrefcnt gInitCount; + +static FILE* gBloatLog = nullptr; +static FILE* gRefcntsLog = nullptr; +static FILE* gAllocLog = nullptr; +static FILE* gCOMPtrLog = nullptr; + +static void +WalkTheStackSavingLocations(std::vector<void*>& aLocations); + +struct SerialNumberRecord +{ + SerialNumberRecord() + : serialNumber(++gNextSerialNumber) + , refCount(0) + , COMPtrCount(0) + {} + + intptr_t serialNumber; + int32_t refCount; + int32_t COMPtrCount; + // We use std:: classes here rather than the XPCOM equivalents because the + // XPCOM equivalents do leak-checking, and if you try to leak-check while + // leak-checking, you're gonna have a bad time. + std::vector<void*> allocationStack; +}; + +struct nsTraceRefcntStats +{ + uint64_t mCreates; + uint64_t mDestroys; + + bool HaveLeaks() const + { + return mCreates != mDestroys; + } + + void Clear() + { + mCreates = 0; + mDestroys = 0; + } + + int64_t NumLeaked() const + { + return (int64_t)(mCreates - mDestroys); + } +}; + +#ifdef DEBUG +static const char kStaticCtorDtorWarning[] = + "XPCOM objects created/destroyed from static ctor/dtor"; + +static void +AssertActivityIsLegal() +{ + if (gActivityTLS == BAD_TLS_INDEX || PR_GetThreadPrivate(gActivityTLS)) { + if (PR_GetEnv("MOZ_FATAL_STATIC_XPCOM_CTORS_DTORS")) { + NS_RUNTIMEABORT(kStaticCtorDtorWarning); + } else { + NS_WARNING(kStaticCtorDtorWarning); + } + } +} +# define ASSERT_ACTIVITY_IS_LEGAL \ + PR_BEGIN_MACRO \ + AssertActivityIsLegal(); \ + PR_END_MACRO +#else +# define ASSERT_ACTIVITY_IS_LEGAL PR_BEGIN_MACRO PR_END_MACRO +#endif // DEBUG + +// These functions are copied from nsprpub/lib/ds/plhash.c, with changes +// to the functions not called Default* to free the SerialNumberRecord or +// the BloatEntry. + +static void* +DefaultAllocTable(void* aPool, size_t aSize) +{ + return PR_MALLOC(aSize); +} + +static void +DefaultFreeTable(void* aPool, void* aItem) +{ + PR_Free(aItem); +} + +static PLHashEntry* +DefaultAllocEntry(void* aPool, const void* aKey) +{ + return PR_NEW(PLHashEntry); +} + +static void +SerialNumberFreeEntry(void* aPool, PLHashEntry* aHashEntry, unsigned aFlag) +{ + if (aFlag == HT_FREE_ENTRY) { + delete static_cast<SerialNumberRecord*>(aHashEntry->value); + PR_Free(aHashEntry); + } +} + +static void +TypesToLogFreeEntry(void* aPool, PLHashEntry* aHashEntry, unsigned aFlag) +{ + if (aFlag == HT_FREE_ENTRY) { + free(const_cast<char*>(static_cast<const char*>(aHashEntry->key))); + PR_Free(aHashEntry); + } +} + +static const PLHashAllocOps serialNumberHashAllocOps = { + DefaultAllocTable, DefaultFreeTable, + DefaultAllocEntry, SerialNumberFreeEntry +}; + +static const PLHashAllocOps typesToLogHashAllocOps = { + DefaultAllocTable, DefaultFreeTable, + DefaultAllocEntry, TypesToLogFreeEntry +}; + +//////////////////////////////////////////////////////////////////////////////// + +class CodeAddressServiceStringTable final +{ +public: + CodeAddressServiceStringTable() : mSet(32) {} + + const char* Intern(const char* aString) + { + nsCharPtrHashKey* e = mSet.PutEntry(aString); + return e->GetKey(); + } + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const + { + return mSet.SizeOfExcludingThis(aMallocSizeOf); + } + +private: + typedef nsTHashtable<nsCharPtrHashKey> StringSet; + StringSet mSet; +}; + +struct CodeAddressServiceStringAlloc final +{ + static char* copy(const char* aStr) { return strdup(aStr); } + static void free(char* aPtr) { ::free(aPtr); } +}; + +// WalkTheStack does not hold any locks needed by MozDescribeCodeAddress, so +// this class does not need to do anything. +struct CodeAddressServiceLock final +{ + static void Unlock() {} + static void Lock() {} + static bool IsLocked() { return true; } +}; + +typedef mozilla::CodeAddressService<CodeAddressServiceStringTable, + CodeAddressServiceStringAlloc, + CodeAddressServiceLock> WalkTheStackCodeAddressService; + +mozilla::StaticAutoPtr<WalkTheStackCodeAddressService> gCodeAddressService; + +//////////////////////////////////////////////////////////////////////////////// + +class BloatEntry +{ +public: + BloatEntry(const char* aClassName, uint32_t aClassSize) + : mClassSize(aClassSize) + { + MOZ_ASSERT(strlen(aClassName) > 0, "BloatEntry name must be non-empty"); + mClassName = PL_strdup(aClassName); + mStats.Clear(); + mTotalLeaked = 0; + } + + ~BloatEntry() + { + PL_strfree(mClassName); + } + + uint32_t GetClassSize() + { + return (uint32_t)mClassSize; + } + const char* GetClassName() + { + return mClassName; + } + + void Ctor() + { + mStats.mCreates++; + } + + void Dtor() + { + mStats.mDestroys++; + } + + static int DumpEntry(PLHashEntry* aHashEntry, int aIndex, void* aArg) + { + BloatEntry* entry = (BloatEntry*)aHashEntry->value; + if (entry) { + static_cast<nsTArray<BloatEntry*>*>(aArg)->AppendElement(entry); + } + return HT_ENUMERATE_NEXT; + } + + static int TotalEntries(PLHashEntry* aHashEntry, int aIndex, void* aArg) + { + BloatEntry* entry = (BloatEntry*)aHashEntry->value; + if (entry && nsCRT::strcmp(entry->mClassName, "TOTAL") != 0) { + entry->Total((BloatEntry*)aArg); + } + return HT_ENUMERATE_NEXT; + } + + void Total(BloatEntry* aTotal) + { + aTotal->mStats.mCreates += mStats.mCreates; + aTotal->mStats.mDestroys += mStats.mDestroys; + aTotal->mClassSize += mClassSize * mStats.mCreates; // adjust for average in DumpTotal + aTotal->mTotalLeaked += mClassSize * mStats.NumLeaked(); + } + + void DumpTotal(FILE* aOut) + { + mClassSize /= mStats.mCreates; + Dump(-1, aOut); + } + + bool PrintDumpHeader(FILE* aOut, const char* aMsg) + { + fprintf(aOut, "\n== BloatView: %s, %s process %d\n", aMsg, + XRE_ChildProcessTypeToString(XRE_GetProcessType()), getpid()); + if (gLogLeaksOnly && !mStats.HaveLeaks()) { + return false; + } + + fprintf(aOut, + "\n" \ + " |<----------------Class--------------->|<-----Bytes------>|<----Objects---->|\n" \ + " | | Per-Inst Leaked| Total Rem|\n"); + + this->DumpTotal(aOut); + + return true; + } + + void Dump(int aIndex, FILE* aOut) + { + if (gLogLeaksOnly && !mStats.HaveLeaks()) { + return; + } + + if (mStats.HaveLeaks() || mStats.mCreates != 0) { + fprintf(aOut, "%4d |%-38.38s| %8d %8" PRId64 "|%8" PRIu64 " %8" PRId64"|\n", + aIndex + 1, mClassName, + GetClassSize(), + nsCRT::strcmp(mClassName, "TOTAL") ? (mStats.NumLeaked() * GetClassSize()) : mTotalLeaked, + mStats.mCreates, + mStats.NumLeaked()); + } + } + +protected: + char* mClassName; + double mClassSize; // This is stored as a double because of the way we compute the avg class size for total bloat. + int64_t mTotalLeaked; // Used only for TOTAL entry. + nsTraceRefcntStats mStats; +}; + +static void +BloatViewFreeEntry(void* aPool, PLHashEntry* aHashEntry, unsigned aFlag) +{ + if (aFlag == HT_FREE_ENTRY) { + BloatEntry* entry = static_cast<BloatEntry*>(aHashEntry->value); + delete entry; + PR_Free(aHashEntry); + } +} + +const static PLHashAllocOps bloatViewHashAllocOps = { + DefaultAllocTable, DefaultFreeTable, + DefaultAllocEntry, BloatViewFreeEntry +}; + +static void +RecreateBloatView() +{ + gBloatView = PL_NewHashTable(256, + PL_HashString, + PL_CompareStrings, + PL_CompareValues, + &bloatViewHashAllocOps, nullptr); +} + +static BloatEntry* +GetBloatEntry(const char* aTypeName, uint32_t aInstanceSize) +{ + if (!gBloatView) { + RecreateBloatView(); + } + BloatEntry* entry = nullptr; + if (gBloatView) { + entry = (BloatEntry*)PL_HashTableLookup(gBloatView, aTypeName); + if (!entry && aInstanceSize > 0) { + + entry = new BloatEntry(aTypeName, aInstanceSize); + PLHashEntry* e = PL_HashTableAdd(gBloatView, aTypeName, entry); + if (!e) { + delete entry; + entry = nullptr; + } + } else { + MOZ_ASSERT(aInstanceSize == 0 || entry->GetClassSize() == aInstanceSize, + "Mismatched sizes were recorded in the memory leak logging table. " + "The usual cause of this is having a templated class that uses " + "MOZ_COUNT_{C,D}TOR in the constructor or destructor, respectively. " + "As a workaround, the MOZ_COUNT_{C,D}TOR calls can be moved to a " + "non-templated base class."); + } + } + return entry; +} + +static int +DumpSerialNumbers(PLHashEntry* aHashEntry, int aIndex, void* aClosure) +{ + SerialNumberRecord* record = + static_cast<SerialNumberRecord*>(aHashEntry->value); + auto* outputFile = static_cast<FILE*>(aClosure); +#ifdef HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR + fprintf(outputFile, "%" PRIdPTR + " @%p (%d references; %d from COMPtrs)\n", + record->serialNumber, + aHashEntry->key, + record->refCount, + record->COMPtrCount); +#else + fprintf(outputFile, "%" PRIdPTR + " @%p (%d references)\n", + record->serialNumber, + aHashEntry->key, + record->refCount); +#endif + if (!record->allocationStack.empty()) { + static const size_t bufLen = 1024; + char buf[bufLen]; + fprintf(outputFile, "allocation stack:\n"); + for (size_t i = 0, length = record->allocationStack.size(); + i < length; + ++i) { + gCodeAddressService->GetLocation(i, record->allocationStack[i], + buf, bufLen); + fprintf(outputFile, "%s\n", buf); + } + } + return HT_ENUMERATE_NEXT; +} + + +template<> +class nsDefaultComparator<BloatEntry*, BloatEntry*> +{ +public: + bool Equals(BloatEntry* const& aEntry1, BloatEntry* const& aEntry2) const + { + return PL_strcmp(aEntry1->GetClassName(), aEntry2->GetClassName()) == 0; + } + bool LessThan(BloatEntry* const& aEntry1, BloatEntry* const& aEntry2) const + { + return PL_strcmp(aEntry1->GetClassName(), aEntry2->GetClassName()) < 0; + } +}; + + +nsresult +nsTraceRefcnt::DumpStatistics() +{ + if (!gBloatLog || !gBloatView) { + return NS_ERROR_FAILURE; + } + + AutoTraceLogLock lock; + + MOZ_ASSERT(!gDumpedStatistics, + "Calling DumpStatistics more than once may result in " + "bogus positive or negative leaks being reported"); + gDumpedStatistics = true; + + // Don't try to log while we hold the lock, we'd deadlock. + AutoRestore<LoggingType> saveLogging(gLogging); + gLogging = NoLogging; + + BloatEntry total("TOTAL", 0); + PL_HashTableEnumerateEntries(gBloatView, BloatEntry::TotalEntries, &total); + const char* msg; + if (gLogLeaksOnly) { + msg = "ALL (cumulative) LEAK STATISTICS"; + } else { + msg = "ALL (cumulative) LEAK AND BLOAT STATISTICS"; + } + const bool leaked = total.PrintDumpHeader(gBloatLog, msg); + + nsTArray<BloatEntry*> entries; + PL_HashTableEnumerateEntries(gBloatView, BloatEntry::DumpEntry, &entries); + const uint32_t count = entries.Length(); + + if (!gLogLeaksOnly || leaked) { + // Sort the entries alphabetically by classname. + entries.Sort(); + + for (uint32_t i = 0; i < count; ++i) { + BloatEntry* entry = entries[i]; + entry->Dump(i, gBloatLog); + } + + fprintf(gBloatLog, "\n"); + } + + fprintf(gBloatLog, "nsTraceRefcnt::DumpStatistics: %d entries\n", count); + + if (gSerialNumbers) { + fprintf(gBloatLog, "\nSerial Numbers of Leaked Objects:\n"); + PL_HashTableEnumerateEntries(gSerialNumbers, DumpSerialNumbers, gBloatLog); + } + + return NS_OK; +} + +void +nsTraceRefcnt::ResetStatistics() +{ + AutoTraceLogLock lock; + if (gBloatView) { + PL_HashTableDestroy(gBloatView); + gBloatView = nullptr; + } +} + +static bool +LogThisType(const char* aTypeName) +{ + void* he = PL_HashTableLookup(gTypesToLog, aTypeName); + return he != nullptr; +} + +static PLHashNumber +HashNumber(const void* aKey) +{ + return PLHashNumber(NS_PTR_TO_INT32(aKey)); +} + +static intptr_t +GetSerialNumber(void* aPtr, bool aCreate) +{ + PLHashEntry** hep = PL_HashTableRawLookup(gSerialNumbers, + HashNumber(aPtr), + aPtr); + if (hep && *hep) { + MOZ_RELEASE_ASSERT(!aCreate, "If an object already has a serial number, we should be destroying it."); + return static_cast<SerialNumberRecord*>((*hep)->value)->serialNumber; + } + + if (!aCreate) { + return 0; + } + + SerialNumberRecord* record = new SerialNumberRecord(); + WalkTheStackSavingLocations(record->allocationStack); + PL_HashTableRawAdd(gSerialNumbers, hep, HashNumber(aPtr), + aPtr, static_cast<void*>(record)); + return gNextSerialNumber; +} + +static int32_t* +GetRefCount(void* aPtr) +{ + PLHashEntry** hep = PL_HashTableRawLookup(gSerialNumbers, + HashNumber(aPtr), + aPtr); + if (hep && *hep) { + return &(static_cast<SerialNumberRecord*>((*hep)->value)->refCount); + } else { + return nullptr; + } +} + +#ifdef HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR +static int32_t* +GetCOMPtrCount(void* aPtr) +{ + PLHashEntry** hep = PL_HashTableRawLookup(gSerialNumbers, + HashNumber(aPtr), + aPtr); + if (hep && *hep) { + return &(static_cast<SerialNumberRecord*>((*hep)->value)->COMPtrCount); + } + return nullptr; +} +#endif // HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR + +static void +RecycleSerialNumberPtr(void* aPtr) +{ + PL_HashTableRemove(gSerialNumbers, aPtr); +} + +static bool +LogThisObj(intptr_t aSerialNumber) +{ + return (bool)PL_HashTableLookup(gObjectsToLog, (const void*)aSerialNumber); +} + +#ifdef XP_WIN +#define FOPEN_NO_INHERIT "N" +#else +#define FOPEN_NO_INHERIT +#endif + +static bool +InitLog(const char* aEnvVar, const char* aMsg, FILE** aResult) +{ + const char* value = getenv(aEnvVar); + if (value) { + if (nsCRT::strcmp(value, "1") == 0) { + *aResult = stdout; + fprintf(stdout, "### %s defined -- logging %s to stdout\n", + aEnvVar, aMsg); + return true; + } else if (nsCRT::strcmp(value, "2") == 0) { + *aResult = stderr; + fprintf(stdout, "### %s defined -- logging %s to stderr\n", + aEnvVar, aMsg); + return true; + } else { + FILE* stream; + nsAutoCString fname(value); + if (!XRE_IsParentProcess()) { + bool hasLogExtension = + fname.RFind(".log", true, -1, 4) == kNotFound ? false : true; + if (hasLogExtension) { + fname.Cut(fname.Length() - 4, 4); + } + fname.Append('_'); + fname.Append((char*)XRE_ChildProcessTypeToString(XRE_GetProcessType())); + fname.AppendLiteral("_pid"); + fname.AppendInt((uint32_t)getpid()); + if (hasLogExtension) { + fname.AppendLiteral(".log"); + } + } + stream = ::fopen(fname.get(), "w" FOPEN_NO_INHERIT); + if (stream) { + MozillaRegisterDebugFD(fileno(stream)); + *aResult = stream; + fprintf(stdout, "### %s defined -- logging %s to %s\n", + aEnvVar, aMsg, fname.get()); + } else { + fprintf(stdout, "### %s defined -- unable to log %s to %s\n", + aEnvVar, aMsg, fname.get()); + MOZ_ASSERT(false, "Tried and failed to create an XPCOM log"); + } + return stream != nullptr; + } + } + return false; +} + + +static void +maybeUnregisterAndCloseFile(FILE*& aFile) +{ + if (!aFile) { + return; + } + + MozillaUnRegisterDebugFILE(aFile); + fclose(aFile); + aFile = nullptr; +} + + +static void +InitTraceLog() +{ + if (gInitialized) { + return; + } + gInitialized = true; + + bool defined = InitLog("XPCOM_MEM_BLOAT_LOG", "bloat/leaks", &gBloatLog); + if (!defined) { + gLogLeaksOnly = InitLog("XPCOM_MEM_LEAK_LOG", "leaks", &gBloatLog); + } + if (defined || gLogLeaksOnly) { + RecreateBloatView(); + if (!gBloatView) { + NS_WARNING("out of memory"); + maybeUnregisterAndCloseFile(gBloatLog); + gLogLeaksOnly = false; + } + } + + InitLog("XPCOM_MEM_REFCNT_LOG", "refcounts", &gRefcntsLog); + + InitLog("XPCOM_MEM_ALLOC_LOG", "new/delete", &gAllocLog); + + const char* classes = getenv("XPCOM_MEM_LOG_CLASSES"); + +#ifdef HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR + if (classes) { + InitLog("XPCOM_MEM_COMPTR_LOG", "nsCOMPtr", &gCOMPtrLog); + } else { + if (getenv("XPCOM_MEM_COMPTR_LOG")) { + fprintf(stdout, "### XPCOM_MEM_COMPTR_LOG defined -- but XPCOM_MEM_LOG_CLASSES is not defined\n"); + } + } +#else + const char* comptr_log = getenv("XPCOM_MEM_COMPTR_LOG"); + if (comptr_log) { + fprintf(stdout, "### XPCOM_MEM_COMPTR_LOG defined -- but it will not work without dynamic_cast\n"); + } +#endif // HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR + + if (classes) { + // if XPCOM_MEM_LOG_CLASSES was set to some value, the value is interpreted + // as a list of class names to track + gTypesToLog = PL_NewHashTable(256, + PL_HashString, + PL_CompareStrings, + PL_CompareValues, + &typesToLogHashAllocOps, nullptr); + if (!gTypesToLog) { + NS_WARNING("out of memory"); + fprintf(stdout, "### XPCOM_MEM_LOG_CLASSES defined -- unable to log specific classes\n"); + } else { + fprintf(stdout, "### XPCOM_MEM_LOG_CLASSES defined -- only logging these classes: "); + const char* cp = classes; + for (;;) { + char* cm = (char*)strchr(cp, ','); + if (cm) { + *cm = '\0'; + } + PL_HashTableAdd(gTypesToLog, strdup(cp), (void*)1); + fprintf(stdout, "%s ", cp); + if (!cm) { + break; + } + *cm = ','; + cp = cm + 1; + } + fprintf(stdout, "\n"); + } + + gSerialNumbers = PL_NewHashTable(256, + HashNumber, + PL_CompareValues, + PL_CompareValues, + &serialNumberHashAllocOps, nullptr); + + + } + + const char* objects = getenv("XPCOM_MEM_LOG_OBJECTS"); + if (objects) { + gObjectsToLog = PL_NewHashTable(256, + HashNumber, + PL_CompareValues, + PL_CompareValues, + nullptr, nullptr); + + if (!gObjectsToLog) { + NS_WARNING("out of memory"); + fprintf(stdout, "### XPCOM_MEM_LOG_OBJECTS defined -- unable to log specific objects\n"); + } else if (!(gRefcntsLog || gAllocLog || gCOMPtrLog)) { + fprintf(stdout, "### XPCOM_MEM_LOG_OBJECTS defined -- but none of XPCOM_MEM_(REFCNT|ALLOC|COMPTR)_LOG is defined\n"); + } else { + fprintf(stdout, "### XPCOM_MEM_LOG_OBJECTS defined -- only logging these objects: "); + const char* cp = objects; + for (;;) { + char* cm = (char*)strchr(cp, ','); + if (cm) { + *cm = '\0'; + } + intptr_t top = 0; + intptr_t bottom = 0; + while (*cp) { + if (*cp == '-') { + bottom = top; + top = 0; + ++cp; + } + top *= 10; + top += *cp - '0'; + ++cp; + } + if (!bottom) { + bottom = top; + } + for (intptr_t serialno = bottom; serialno <= top; serialno++) { + PL_HashTableAdd(gObjectsToLog, (const void*)serialno, (void*)1); + fprintf(stdout, "%" PRIdPTR " ", serialno); + } + if (!cm) { + break; + } + *cm = ','; + cp = cm + 1; + } + fprintf(stdout, "\n"); + } + } + + + if (gBloatLog) { + gLogging = OnlyBloatLogging; + } + + if (gRefcntsLog || gAllocLog || gCOMPtrLog) { + gLogging = FullLogging; + } +} + + +extern "C" { + +static void +PrintStackFrame(uint32_t aFrameNumber, void* aPC, void* aSP, void* aClosure) +{ + FILE* stream = (FILE*)aClosure; + MozCodeAddressDetails details; + char buf[1024]; + + MozDescribeCodeAddress(aPC, &details); + MozFormatCodeAddressDetails(buf, sizeof(buf), aFrameNumber, aPC, &details); + fprintf(stream, "%s\n", buf); + fflush(stream); +} + +static void +PrintStackFrameCached(uint32_t aFrameNumber, void* aPC, void* aSP, + void* aClosure) +{ + auto stream = static_cast<FILE*>(aClosure); + static const size_t buflen = 1024; + char buf[buflen]; + gCodeAddressService->GetLocation(aFrameNumber, aPC, buf, buflen); + fprintf(stream, " %s\n", buf); + fflush(stream); +} + +static void +RecordStackFrame(uint32_t /*aFrameNumber*/, void* aPC, void* /*aSP*/, + void* aClosure) +{ + auto locations = static_cast<std::vector<void*>*>(aClosure); + locations->push_back(aPC); +} + +} + +void +nsTraceRefcnt::WalkTheStack(FILE* aStream) +{ + MozStackWalk(PrintStackFrame, /* skipFrames */ 2, /* maxFrames */ 0, aStream, + 0, nullptr); +} + +/** + * This is a variant of |WalkTheStack| that uses |CodeAddressService| to cache + * the results of |NS_DescribeCodeAddress|. If |WalkTheStackCached| is being + * called frequently, it will be a few orders of magnitude faster than + * |WalkTheStack|. However, the cache uses a lot of memory, which can cause + * OOM crashes. Therefore, this should only be used for things like refcount + * logging which walk the stack extremely frequently. + */ +static void +WalkTheStackCached(FILE* aStream) +{ + if (!gCodeAddressService) { + gCodeAddressService = new WalkTheStackCodeAddressService(); + } + MozStackWalk(PrintStackFrameCached, /* skipFrames */ 2, /* maxFrames */ 0, + aStream, 0, nullptr); +} + +static void +WalkTheStackSavingLocations(std::vector<void*>& aLocations) +{ + if (!gCodeAddressService) { + gCodeAddressService = new WalkTheStackCodeAddressService(); + } + static const int kFramesToSkip = + 0 + // this frame gets inlined + 1 + // GetSerialNumber + 1; // NS_LogCtor + MozStackWalk(RecordStackFrame, kFramesToSkip, /* maxFrames */ 0, + &aLocations, 0, nullptr); +} + +//---------------------------------------------------------------------- + +EXPORT_XPCOM_API(void) +NS_LogInit() +{ + NS_SetMainThread(); + + // FIXME: This is called multiple times, we should probably not allow that. + StackWalkInitCriticalAddress(); + if (++gInitCount) { + nsTraceRefcnt::SetActivityIsLegal(true); + } +} + +EXPORT_XPCOM_API(void) +NS_LogTerm() +{ + mozilla::LogTerm(); +} + +#ifdef MOZ_DMD +// If MOZ_DMD_SHUTDOWN_LOG is set, dump a DMD report to a file. +// The value of this environment variable is used as the prefix +// of the file name, so you probably want something like "/tmp/". +// By default, this is run in all processes, but you can record a +// log only for a specific process type by setting MOZ_DMD_LOG_PROCESS +// to the process type you want to log, such as "default" or "tab". +// This method can't use the higher level XPCOM file utilities +// because it is run very late in shutdown to avoid recording +// information about refcount logging entries. +static void +LogDMDFile() +{ + const char* dmdFilePrefix = PR_GetEnv("MOZ_DMD_SHUTDOWN_LOG"); + if (!dmdFilePrefix) { + return; + } + + const char* logProcessEnv = PR_GetEnv("MOZ_DMD_LOG_PROCESS"); + if (logProcessEnv && !!strcmp(logProcessEnv, XRE_ChildProcessTypeToString(XRE_GetProcessType()))) { + return; + } + + nsPrintfCString fileName("%sdmd-%d.log.gz", dmdFilePrefix, base::GetCurrentProcId()); + FILE* logFile = fopen(fileName.get(), "w"); + if (NS_WARN_IF(!logFile)) { + return; + } + + nsMemoryInfoDumper::DumpDMDToFile(logFile); +} +#endif // MOZ_DMD + +namespace mozilla { +void +LogTerm() +{ + NS_ASSERTION(gInitCount > 0, + "NS_LogTerm without matching NS_LogInit"); + + if (--gInitCount == 0) { +#ifdef DEBUG + /* FIXME bug 491977: This is only going to operate on the + * BlockingResourceBase which is compiled into + * libxul/libxpcom_core.so. Anyone using external linkage will + * have their own copy of BlockingResourceBase statics which will + * not be freed by this method. + * + * It sounds like what we really want is to be able to register a + * callback function to call at XPCOM shutdown. Note that with + * this solution, however, we need to guarantee that + * BlockingResourceBase::Shutdown() runs after all other shutdown + * functions. + */ + BlockingResourceBase::Shutdown(); +#endif + + if (gInitialized) { + nsTraceRefcnt::DumpStatistics(); + nsTraceRefcnt::ResetStatistics(); + } + nsTraceRefcnt::Shutdown(); + nsTraceRefcnt::SetActivityIsLegal(false); + gActivityTLS = BAD_TLS_INDEX; + +#ifdef MOZ_DMD + LogDMDFile(); +#endif + } +} + +} // namespace mozilla + +EXPORT_XPCOM_API(void) +NS_LogAddRef(void* aPtr, nsrefcnt aRefcnt, + const char* aClass, uint32_t aClassSize) +{ + ASSERT_ACTIVITY_IS_LEGAL; + if (!gInitialized) { + InitTraceLog(); + } + if (gLogging == NoLogging) { + return; + } + if (aRefcnt == 1 || gLogging == FullLogging) { + AutoTraceLogLock lock; + + if (aRefcnt == 1 && gBloatLog) { + BloatEntry* entry = GetBloatEntry(aClass, aClassSize); + if (entry) { + entry->Ctor(); + } + } + + // Here's the case where MOZ_COUNT_CTOR was not used, + // yet we still want to see creation information: + + bool loggingThisType = (!gTypesToLog || LogThisType(aClass)); + intptr_t serialno = 0; + if (gSerialNumbers && loggingThisType) { + serialno = GetSerialNumber(aPtr, aRefcnt == 1); + MOZ_ASSERT(serialno != 0, + "Serial number requested for unrecognized pointer! " + "Are you memmoving a refcounted object?"); + int32_t* count = GetRefCount(aPtr); + if (count) { + (*count)++; + } + + } + + bool loggingThisObject = (!gObjectsToLog || LogThisObj(serialno)); + if (aRefcnt == 1 && gAllocLog && loggingThisType && loggingThisObject) { + fprintf(gAllocLog, "\n<%s> %p %" PRIdPTR " Create [thread %p]\n", aClass, aPtr, serialno, PR_GetCurrentThread()); + WalkTheStackCached(gAllocLog); + } + + if (gRefcntsLog && loggingThisType && loggingThisObject) { + // Can't use MOZ_LOG(), b/c it truncates the line + fprintf(gRefcntsLog, "\n<%s> %p %" PRIuPTR " AddRef %" PRIuPTR " [thread %p]\n", + aClass, aPtr, serialno, aRefcnt, PR_GetCurrentThread()); + WalkTheStackCached(gRefcntsLog); + fflush(gRefcntsLog); + } + } +} + +EXPORT_XPCOM_API(void) +NS_LogRelease(void* aPtr, nsrefcnt aRefcnt, const char* aClass) +{ + ASSERT_ACTIVITY_IS_LEGAL; + if (!gInitialized) { + InitTraceLog(); + } + if (gLogging == NoLogging) { + return; + } + if (aRefcnt == 0 || gLogging == FullLogging) { + AutoTraceLogLock lock; + + if (aRefcnt == 0 && gBloatLog) { + BloatEntry* entry = GetBloatEntry(aClass, 0); + if (entry) { + entry->Dtor(); + } + } + + bool loggingThisType = (!gTypesToLog || LogThisType(aClass)); + intptr_t serialno = 0; + if (gSerialNumbers && loggingThisType) { + serialno = GetSerialNumber(aPtr, false); + MOZ_ASSERT(serialno != 0, + "Serial number requested for unrecognized pointer! " + "Are you memmoving a refcounted object?"); + int32_t* count = GetRefCount(aPtr); + if (count) { + (*count)--; + } + + } + + bool loggingThisObject = (!gObjectsToLog || LogThisObj(serialno)); + if (gRefcntsLog && loggingThisType && loggingThisObject) { + // Can't use MOZ_LOG(), b/c it truncates the line + fprintf(gRefcntsLog, + "\n<%s> %p %" PRIuPTR " Release %" PRIuPTR " [thread %p]\n", + aClass, aPtr, serialno, aRefcnt, PR_GetCurrentThread()); + WalkTheStackCached(gRefcntsLog); + fflush(gRefcntsLog); + } + + // Here's the case where MOZ_COUNT_DTOR was not used, + // yet we still want to see deletion information: + + if (aRefcnt == 0 && gAllocLog && loggingThisType && loggingThisObject) { + fprintf(gAllocLog, "\n<%s> %p %" PRIdPTR " Destroy [thread %p]\n", aClass, aPtr, serialno, PR_GetCurrentThread()); + WalkTheStackCached(gAllocLog); + } + + if (aRefcnt == 0 && gSerialNumbers && loggingThisType) { + RecycleSerialNumberPtr(aPtr); + } + } +} + +EXPORT_XPCOM_API(void) +NS_LogCtor(void* aPtr, const char* aType, uint32_t aInstanceSize) +{ + ASSERT_ACTIVITY_IS_LEGAL; + if (!gInitialized) { + InitTraceLog(); + } + + if (gLogging == NoLogging) { + return; + } + + AutoTraceLogLock lock; + + if (gBloatLog) { + BloatEntry* entry = GetBloatEntry(aType, aInstanceSize); + if (entry) { + entry->Ctor(); + } + } + + bool loggingThisType = (!gTypesToLog || LogThisType(aType)); + intptr_t serialno = 0; + if (gSerialNumbers && loggingThisType) { + serialno = GetSerialNumber(aPtr, true); + MOZ_ASSERT(serialno != 0, "GetSerialNumber should never return 0 when passed true"); + } + + bool loggingThisObject = (!gObjectsToLog || LogThisObj(serialno)); + if (gAllocLog && loggingThisType && loggingThisObject) { + fprintf(gAllocLog, "\n<%s> %p %" PRIdPTR " Ctor (%d)\n", + aType, aPtr, serialno, aInstanceSize); + WalkTheStackCached(gAllocLog); + } +} + + +EXPORT_XPCOM_API(void) +NS_LogDtor(void* aPtr, const char* aType, uint32_t aInstanceSize) +{ + ASSERT_ACTIVITY_IS_LEGAL; + if (!gInitialized) { + InitTraceLog(); + } + + if (gLogging == NoLogging) { + return; + } + + AutoTraceLogLock lock; + + if (gBloatLog) { + BloatEntry* entry = GetBloatEntry(aType, aInstanceSize); + if (entry) { + entry->Dtor(); + } + } + + bool loggingThisType = (!gTypesToLog || LogThisType(aType)); + intptr_t serialno = 0; + if (gSerialNumbers && loggingThisType) { + serialno = GetSerialNumber(aPtr, false); + MOZ_ASSERT(serialno != 0, + "Serial number requested for unrecognized pointer! " + "Are you memmoving a MOZ_COUNT_CTOR-tracked object?"); + RecycleSerialNumberPtr(aPtr); + } + + bool loggingThisObject = (!gObjectsToLog || LogThisObj(serialno)); + + // (If we're on a losing architecture, don't do this because we'll be + // using LogDeleteXPCOM instead to get file and line numbers.) + if (gAllocLog && loggingThisType && loggingThisObject) { + fprintf(gAllocLog, "\n<%s> %p %" PRIdPTR " Dtor (%d)\n", + aType, aPtr, serialno, aInstanceSize); + WalkTheStackCached(gAllocLog); + } +} + + +EXPORT_XPCOM_API(void) +NS_LogCOMPtrAddRef(void* aCOMPtr, nsISupports* aObject) +{ +#ifdef HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR + // Get the most-derived object. + void* object = dynamic_cast<void*>(aObject); + + // This is a very indirect way of finding out what the class is + // of the object being logged. If we're logging a specific type, + // then + if (!gTypesToLog || !gSerialNumbers) { + return; + } + if (!gInitialized) { + InitTraceLog(); + } + if (gLogging == FullLogging) { + AutoTraceLogLock lock; + + intptr_t serialno = GetSerialNumber(object, false); + if (serialno == 0) { + return; + } + + int32_t* count = GetCOMPtrCount(object); + if (count) { + (*count)++; + } + + bool loggingThisObject = (!gObjectsToLog || LogThisObj(serialno)); + + if (gCOMPtrLog && loggingThisObject) { + fprintf(gCOMPtrLog, "\n<?> %p %" PRIdPTR " nsCOMPtrAddRef %d %p\n", + object, serialno, count ? (*count) : -1, aCOMPtr); + WalkTheStackCached(gCOMPtrLog); + } + } +#endif // HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR +} + + +EXPORT_XPCOM_API(void) +NS_LogCOMPtrRelease(void* aCOMPtr, nsISupports* aObject) +{ +#ifdef HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR + // Get the most-derived object. + void* object = dynamic_cast<void*>(aObject); + + // This is a very indirect way of finding out what the class is + // of the object being logged. If we're logging a specific type, + // then + if (!gTypesToLog || !gSerialNumbers) { + return; + } + if (!gInitialized) { + InitTraceLog(); + } + if (gLogging == FullLogging) { + AutoTraceLogLock lock; + + intptr_t serialno = GetSerialNumber(object, false); + if (serialno == 0) { + return; + } + + int32_t* count = GetCOMPtrCount(object); + if (count) { + (*count)--; + } + + bool loggingThisObject = (!gObjectsToLog || LogThisObj(serialno)); + + if (gCOMPtrLog && loggingThisObject) { + fprintf(gCOMPtrLog, "\n<?> %p %" PRIdPTR " nsCOMPtrRelease %d %p\n", + object, serialno, count ? (*count) : -1, aCOMPtr); + WalkTheStackCached(gCOMPtrLog); + } + } +#endif // HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR +} + +void +nsTraceRefcnt::Shutdown() +{ + gCodeAddressService = nullptr; + if (gBloatView) { + PL_HashTableDestroy(gBloatView); + gBloatView = nullptr; + } + if (gTypesToLog) { + PL_HashTableDestroy(gTypesToLog); + gTypesToLog = nullptr; + } + if (gObjectsToLog) { + PL_HashTableDestroy(gObjectsToLog); + gObjectsToLog = nullptr; + } + if (gSerialNumbers) { + PL_HashTableDestroy(gSerialNumbers); + gSerialNumbers = nullptr; + } + maybeUnregisterAndCloseFile(gBloatLog); + maybeUnregisterAndCloseFile(gRefcntsLog); + maybeUnregisterAndCloseFile(gAllocLog); + maybeUnregisterAndCloseFile(gCOMPtrLog); +} + +void +nsTraceRefcnt::SetActivityIsLegal(bool aLegal) +{ + if (gActivityTLS == BAD_TLS_INDEX) { + PR_NewThreadPrivateIndex(&gActivityTLS, nullptr); + } + + PR_SetThreadPrivate(gActivityTLS, reinterpret_cast<void*>(!aLegal)); +} diff --git a/xpcom/base/nsTraceRefcnt.h b/xpcom/base/nsTraceRefcnt.h new file mode 100644 index 000000000..73e123a3f --- /dev/null +++ b/xpcom/base/nsTraceRefcnt.h @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef nsTraceRefcnt_h___ +#define nsTraceRefcnt_h___ + +#include <stdio.h> // for FILE +#include "nscore.h" + +class nsTraceRefcnt +{ +public: + static void Shutdown(); + + static nsresult DumpStatistics(); + + static void ResetStatistics(); + + static void WalkTheStack(FILE* aStream); + + /** + * Tell nsTraceRefcnt whether refcounting, allocation, and destruction + * activity is legal. This is used to trigger assertions for any such + * activity that occurs because of static constructors or destructors. + */ + static void SetActivityIsLegal(bool aLegal); +}; + +//////////////////////////////////////////////////////////////////////////////// +// And now for that utility that you've all been asking for... + +extern "C" void +NS_MeanAndStdDev(double aNumberOfValues, + double aSumOfValues, double aSumOfSquaredValues, + double* aMeanResult, double* aStdDevResult); + +//////////////////////////////////////////////////////////////////////////////// +#endif diff --git a/xpcom/base/nsUUIDGenerator.cpp b/xpcom/base/nsUUIDGenerator.cpp new file mode 100644 index 000000000..254a01322 --- /dev/null +++ b/xpcom/base/nsUUIDGenerator.cpp @@ -0,0 +1,177 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#if defined(XP_WIN) +#include <windows.h> +#include <objbase.h> +#elif defined(XP_MACOSX) +#include <CoreFoundation/CoreFoundation.h> +#else +#include <stdlib.h> +#include "prrng.h" +#endif + +#include "nsUUIDGenerator.h" + +#ifdef ANDROID +extern "C" NS_EXPORT void arc4random_buf(void*, size_t); +#endif + +using namespace mozilla; + +NS_IMPL_ISUPPORTS(nsUUIDGenerator, nsIUUIDGenerator) + +nsUUIDGenerator::nsUUIDGenerator() + : mLock("nsUUIDGenerator.mLock") +{ +} + +nsUUIDGenerator::~nsUUIDGenerator() +{ +} + +nsresult +nsUUIDGenerator::Init() +{ + // We're a service, so we're guaranteed that Init() is not going + // to be reentered while we're inside Init(). + +#if !defined(XP_WIN) && !defined(XP_MACOSX) && !defined(HAVE_ARC4RANDOM) + /* initialize random number generator using NSPR random noise */ + unsigned int seed; + + size_t bytes = 0; + while (bytes < sizeof(seed)) { + size_t nbytes = PR_GetRandomNoise(((unsigned char*)&seed) + bytes, + sizeof(seed) - bytes); + if (nbytes == 0) { + return NS_ERROR_FAILURE; + } + bytes += nbytes; + } + + /* Initialize a new RNG state, and immediately switch + * back to the previous one -- we want to use mState + * only for our own calls to random(). + */ + mSavedState = initstate(seed, mState, sizeof(mState)); + setstate(mSavedState); + + mRBytes = 4; +#ifdef RAND_MAX + if ((unsigned long)RAND_MAX < 0xffffffffUL) { + mRBytes = 3; + } + if ((unsigned long)RAND_MAX < 0x00ffffffUL) { + mRBytes = 2; + } + if ((unsigned long)RAND_MAX < 0x0000ffffUL) { + mRBytes = 1; + } + if ((unsigned long)RAND_MAX < 0x000000ffUL) { + return NS_ERROR_FAILURE; + } +#endif + +#endif /* non XP_WIN and non XP_MACOSX and non ARC4RANDOM */ + + return NS_OK; +} + +NS_IMETHODIMP +nsUUIDGenerator::GenerateUUID(nsID** aRet) +{ + nsID* id = static_cast<nsID*>(moz_xmalloc(sizeof(nsID))); + if (!id) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nsresult rv = GenerateUUIDInPlace(id); + if (NS_FAILED(rv)) { + free(id); + return rv; + } + + *aRet = id; + return rv; +} + +NS_IMETHODIMP +nsUUIDGenerator::GenerateUUIDInPlace(nsID* aId) +{ + // The various code in this method is probably not threadsafe, so lock + // across the whole method. + MutexAutoLock lock(mLock); + +#if defined(XP_WIN) + HRESULT hr = CoCreateGuid((GUID*)aId); + if (FAILED(hr)) { + return NS_ERROR_FAILURE; + } +#elif defined(XP_MACOSX) + CFUUIDRef uuid = CFUUIDCreate(kCFAllocatorDefault); + if (!uuid) { + return NS_ERROR_FAILURE; + } + + CFUUIDBytes bytes = CFUUIDGetUUIDBytes(uuid); + memcpy(aId, &bytes, sizeof(nsID)); + + CFRelease(uuid); +#else /* not windows or OS X; generate randomness using random(). */ + /* XXX we should be saving the return of setstate here and switching + * back to it; instead, we use the value returned when we called + * initstate, since older glibc's have broken setstate() return values + */ +#ifndef HAVE_ARC4RANDOM + setstate(mState); +#endif + +#ifdef HAVE_ARC4RANDOM_BUF + arc4random_buf(aId, sizeof(nsID)); +#else /* HAVE_ARC4RANDOM_BUF */ + size_t bytesLeft = sizeof(nsID); + while (bytesLeft > 0) { +#ifdef HAVE_ARC4RANDOM + long rval = arc4random(); + const size_t mRBytes = 4; +#else + long rval = random(); +#endif + + + uint8_t* src = (uint8_t*)&rval; + // We want to grab the mRBytes least significant bytes of rval, since + // mRBytes less than sizeof(rval) means the high bytes are 0. +#ifdef IS_BIG_ENDIAN + src += sizeof(rval) - mRBytes; +#endif + uint8_t* dst = ((uint8_t*)aId) + (sizeof(nsID) - bytesLeft); + size_t toWrite = (bytesLeft < mRBytes ? bytesLeft : mRBytes); + for (size_t i = 0; i < toWrite; i++) { + dst[i] = src[i]; + } + + bytesLeft -= toWrite; + } +#endif /* HAVE_ARC4RANDOM_BUF */ + + /* Put in the version */ + aId->m2 &= 0x0fff; + aId->m2 |= 0x4000; + + /* Put in the variant */ + aId->m3[0] &= 0x3f; + aId->m3[0] |= 0x80; + +#ifndef HAVE_ARC4RANDOM + /* Restore the previous RNG state */ + setstate(mSavedState); +#endif +#endif + + return NS_OK; +} diff --git a/xpcom/base/nsUUIDGenerator.h b/xpcom/base/nsUUIDGenerator.h new file mode 100644 index 000000000..dd86093f8 --- /dev/null +++ b/xpcom/base/nsUUIDGenerator.h @@ -0,0 +1,44 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _NSUUIDGENERATOR_H_ +#define _NSUUIDGENERATOR_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/Mutex.h" + +#include "nsIUUIDGenerator.h" + +class nsUUIDGenerator final : public nsIUUIDGenerator +{ +public: + nsUUIDGenerator(); + + NS_DECL_THREADSAFE_ISUPPORTS + + NS_DECL_NSIUUIDGENERATOR + + nsresult Init(); + +private: + ~nsUUIDGenerator(); + +protected: + + mozilla::Mutex mLock; +#if !defined(XP_WIN) && !defined(XP_MACOSX) && !defined(HAVE_ARC4RANDOM) + char mState[128]; + char* mSavedState; + uint8_t mRBytes; +#endif +}; + +#define NS_UUID_GENERATOR_CONTRACTID "@mozilla.org/uuid-generator;1" +#define NS_UUID_GENERATOR_CID \ +{ 0x706d36bb, 0xbf79, 0x4293, \ +{ 0x81, 0xf2, 0x8f, 0x68, 0x28, 0xc1, 0x8f, 0x9d } } + +#endif /* _NSUUIDGENERATOR_H_ */ diff --git a/xpcom/base/nsVersionComparatorImpl.cpp b/xpcom/base/nsVersionComparatorImpl.cpp new file mode 100644 index 000000000..8a37d8b1a --- /dev/null +++ b/xpcom/base/nsVersionComparatorImpl.cpp @@ -0,0 +1,22 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsVersionComparatorImpl.h" +#include "nsVersionComparator.h" +#include "nsString.h" + +NS_IMPL_ISUPPORTS(nsVersionComparatorImpl, nsIVersionComparator) + +NS_IMETHODIMP +nsVersionComparatorImpl::Compare(const nsACString& aStr1, + const nsACString& aStr2, + int32_t* aResult) +{ + *aResult = mozilla::CompareVersions(PromiseFlatCString(aStr1).get(), + PromiseFlatCString(aStr2).get()); + + return NS_OK; +} diff --git a/xpcom/base/nsVersionComparatorImpl.h b/xpcom/base/nsVersionComparatorImpl.h new file mode 100644 index 000000000..84a76d1bb --- /dev/null +++ b/xpcom/base/nsVersionComparatorImpl.h @@ -0,0 +1,25 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Attributes.h" + +#include "nsIVersionComparator.h" + +class nsVersionComparatorImpl final : public nsIVersionComparator +{ + ~nsVersionComparatorImpl() {} + +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIVERSIONCOMPARATOR +}; + +#define NS_VERSIONCOMPARATOR_CONTRACTID "@mozilla.org/xpcom/version-comparator;1" + +// c6e47036-ca94-4be3-963a-9abd8705f7a8 +#define NS_VERSIONCOMPARATOR_CID \ +{ 0xc6e47036, 0xca94, 0x4be3, \ + { 0x96, 0x3a, 0x9a, 0xbd, 0x87, 0x05, 0xf7, 0xa8 } } diff --git a/xpcom/base/nsWeakPtr.h b/xpcom/base/nsWeakPtr.h new file mode 100644 index 000000000..e2f7c37f1 --- /dev/null +++ b/xpcom/base/nsWeakPtr.h @@ -0,0 +1,15 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsWeakPtr_h__ +#define nsWeakPtr_h__ + +#include "nsIWeakReference.h" +#include "nsCOMPtr.h" + +// typedef nsCOMPtr<nsIWeakReference> nsWeakPtr; + +#endif diff --git a/xpcom/base/nsWindowsHelpers.h b/xpcom/base/nsWindowsHelpers.h new file mode 100644 index 000000000..66505b345 --- /dev/null +++ b/xpcom/base/nsWindowsHelpers.h @@ -0,0 +1,371 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsWindowsHelpers_h +#define nsWindowsHelpers_h + +#include <windows.h> +#include "nsAutoRef.h" +#include "nscore.h" +#include "mozilla/Assertions.h" + +// ---------------------------------------------------------------------------- +// Critical Section helper class +// ---------------------------------------------------------------------------- + +class AutoCriticalSection +{ +public: + AutoCriticalSection(LPCRITICAL_SECTION aSection) + : mSection(aSection) + { + ::EnterCriticalSection(mSection); + } + ~AutoCriticalSection() + { + ::LeaveCriticalSection(mSection); + } +private: + LPCRITICAL_SECTION mSection; +}; + +template<> +class nsAutoRefTraits<HKEY> +{ +public: + typedef HKEY RawRef; + static HKEY Void() + { + return nullptr; + } + + static void Release(RawRef aFD) + { + if (aFD != Void()) { + RegCloseKey(aFD); + } + } +}; + +template<> +class nsAutoRefTraits<HDC> +{ +public: + typedef HDC RawRef; + static HDC Void() + { + return nullptr; + } + + static void Release(RawRef aFD) + { + if (aFD != Void()) { + ::DeleteDC(aFD); + } + } +}; + +template<> +class nsAutoRefTraits<HBRUSH> +{ +public: + typedef HBRUSH RawRef; + static HBRUSH Void() + { + return nullptr; + } + + static void Release(RawRef aFD) + { + if (aFD != Void()) { + ::DeleteObject(aFD); + } + } +}; + +template<> +class nsAutoRefTraits<HRGN> +{ +public: + typedef HRGN RawRef; + static HRGN Void() + { + return nullptr; + } + + static void Release(RawRef aFD) + { + if (aFD != Void()) { + ::DeleteObject(aFD); + } + } +}; + +template<> +class nsAutoRefTraits<HBITMAP> +{ +public: + typedef HBITMAP RawRef; + static HBITMAP Void() + { + return nullptr; + } + + static void Release(RawRef aFD) + { + if (aFD != Void()) { + ::DeleteObject(aFD); + } + } +}; + +template<> +class nsAutoRefTraits<SC_HANDLE> +{ +public: + typedef SC_HANDLE RawRef; + static SC_HANDLE Void() + { + return nullptr; + } + + static void Release(RawRef aFD) + { + if (aFD != Void()) { + CloseServiceHandle(aFD); + } + } +}; + +template<> +class nsSimpleRef<HANDLE> +{ +protected: + typedef HANDLE RawRef; + + nsSimpleRef() : mRawRef(nullptr) + { + } + + nsSimpleRef(RawRef aRawRef) : mRawRef(aRawRef) + { + } + + bool HaveResource() const + { + return mRawRef && mRawRef != INVALID_HANDLE_VALUE; + } + +public: + RawRef get() const + { + return mRawRef; + } + + static void Release(RawRef aRawRef) + { + if (aRawRef && aRawRef != INVALID_HANDLE_VALUE) { + CloseHandle(aRawRef); + } + } + RawRef mRawRef; +}; + + +template<> +class nsAutoRefTraits<HMODULE> +{ +public: + typedef HMODULE RawRef; + static RawRef Void() + { + return nullptr; + } + + static void Release(RawRef aFD) + { + if (aFD != Void()) { + FreeLibrary(aFD); + } + } +}; + + +template<> +class nsAutoRefTraits<DEVMODEW*> +{ +public: + typedef DEVMODEW* RawRef; + static RawRef Void() + { + return nullptr; + } + + static void Release(RawRef aDevMode) + { + if (aDevMode != Void()) { + ::HeapFree(::GetProcessHeap(), 0, aDevMode); + } + } +}; + + +// HGLOBAL is just a typedef of HANDLE which nsSimpleRef has a specialization of, +// that means having a nsAutoRefTraits specialization for HGLOBAL is useless. +// Therefore we create a wrapper class for HGLOBAL to make nsAutoRefTraits and +// nsAutoRef work as intention. +class nsHGLOBAL { +public: + nsHGLOBAL(HGLOBAL hGlobal) : m_hGlobal(hGlobal) + { + } + + operator HGLOBAL() const + { + return m_hGlobal; + } + +private: + HGLOBAL m_hGlobal; +}; + + +template<> +class nsAutoRefTraits<nsHGLOBAL> +{ +public: + typedef nsHGLOBAL RawRef; + static RawRef Void() + { + return nullptr; + } + + static void Release(RawRef hGlobal) + { + ::GlobalFree(hGlobal); + } +}; + + +// Because Printer's HANDLE uses ClosePrinter and we already have nsAutoRef<HANDLE> +// which uses CloseHandle so we need to create a wrapper class for HANDLE to have +// another specialization for nsAutoRefTraits. +class nsHPRINTER { +public: + nsHPRINTER(HANDLE hPrinter) : m_hPrinter(hPrinter) + { + } + + operator HANDLE() const + { + return m_hPrinter; + } + + HANDLE* operator&() + { + return &m_hPrinter; + } + +private: + HANDLE m_hPrinter; +}; + + +// winspool.h header has AddMonitor macro, it conflicts with AddMonitor member +// function in TaskbarPreview.cpp and TaskbarTabPreview.cpp. Beside, we only +// need ClosePrinter here for Release function, so having its prototype is enough. +extern "C" BOOL WINAPI ClosePrinter(HANDLE hPrinter); + + +template<> +class nsAutoRefTraits<nsHPRINTER> +{ +public: + typedef nsHPRINTER RawRef; + static RawRef Void() + { + return nullptr; + } + + static void Release(RawRef hPrinter) + { + ::ClosePrinter(hPrinter); + } +}; + + +typedef nsAutoRef<HKEY> nsAutoRegKey; +typedef nsAutoRef<HDC> nsAutoHDC; +typedef nsAutoRef<HBRUSH> nsAutoBrush; +typedef nsAutoRef<HRGN> nsAutoRegion; +typedef nsAutoRef<HBITMAP> nsAutoBitmap; +typedef nsAutoRef<SC_HANDLE> nsAutoServiceHandle; +typedef nsAutoRef<HANDLE> nsAutoHandle; +typedef nsAutoRef<HMODULE> nsModuleHandle; +typedef nsAutoRef<DEVMODEW*> nsAutoDevMode; +typedef nsAutoRef<nsHGLOBAL> nsAutoGlobalMem; +typedef nsAutoRef<nsHPRINTER> nsAutoPrinter; + +namespace { + +// Construct a path "<system32>\<aModule>". return false if the output buffer +// is too small. +// Note: If the system path cannot be found, or doesn't fit in the output buffer +// with the module name, we will just ignore the system path and output the +// module name alone; +// this may mean using a normal search path wherever the output is used. +bool inline +ConstructSystem32Path(LPCWSTR aModule, WCHAR* aSystemPath, UINT aSize) +{ + MOZ_ASSERT(aSystemPath); + + size_t fileLen = wcslen(aModule); + if (fileLen >= aSize) { + // The module name alone cannot even fit! + return false; + } + + size_t systemDirLen = GetSystemDirectoryW(aSystemPath, aSize); + + if (systemDirLen) { + if (systemDirLen < aSize - fileLen) { + // Make the system directory path terminate with a slash. + if (aSystemPath[systemDirLen - 1] != L'\\') { + if (systemDirLen + 1 < aSize - fileLen) { + aSystemPath[systemDirLen] = L'\\'; + ++systemDirLen; + // No need to re-nullptr terminate. + } else { + // Couldn't fit the system path with added slash. + systemDirLen = 0; + } + } + } else { + // Couldn't fit the system path. + systemDirLen = 0; + } + } + + MOZ_ASSERT(systemDirLen + fileLen < aSize); + + wcsncpy(aSystemPath + systemDirLen, aModule, fileLen); + aSystemPath[systemDirLen + fileLen] = L'\0'; + return true; +} + +HMODULE inline +LoadLibrarySystem32(LPCWSTR aModule) +{ + WCHAR systemPath[MAX_PATH + 1]; + if (!ConstructSystem32Path(aModule, systemPath, MAX_PATH + 1)) { + return NULL; + } + return LoadLibraryW(systemPath); +} + +} + +#endif diff --git a/xpcom/base/nscore.h b/xpcom/base/nscore.h new file mode 100644 index 000000000..f6e73c6bc --- /dev/null +++ b/xpcom/base/nscore.h @@ -0,0 +1,288 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nscore_h___ +#define nscore_h___ + +/** + * Make sure that we have the proper platform specific + * c++ definitions needed by nscore.h + */ +#ifndef _XPCOM_CONFIG_H_ +#include "xpcom-config.h" +#endif + +/* Definitions of functions and operators that allocate memory. */ +#if !defined(XPCOM_GLUE) && !defined(NS_NO_XPCOM) && !defined(MOZ_NO_MOZALLOC) +# include "mozilla/mozalloc.h" +#endif + +/** + * Incorporate the integer data types which XPCOM uses. + */ +#include <stddef.h> +#include <stdint.h> + +#include "mozilla/RefCountType.h" + +/* Core XPCOM declarations. */ + +/*----------------------------------------------------------------------*/ +/* Import/export defines */ + +#ifdef HAVE_VISIBILITY_HIDDEN_ATTRIBUTE +#define NS_VISIBILITY_HIDDEN __attribute__ ((visibility ("hidden"))) +#else +#define NS_VISIBILITY_HIDDEN +#endif + +#if defined(HAVE_VISIBILITY_ATTRIBUTE) +#define NS_VISIBILITY_DEFAULT __attribute__ ((visibility ("default"))) +#elif defined(__SUNPRO_C) || defined(__SUNPRO_CC) +#define NS_VISIBILITY_DEFAULT __global +#else +#define NS_VISIBILITY_DEFAULT +#endif + +#define NS_HIDDEN_(type) NS_VISIBILITY_HIDDEN type +#define NS_EXTERNAL_VIS_(type) NS_VISIBILITY_DEFAULT type + +#define NS_HIDDEN NS_VISIBILITY_HIDDEN +#define NS_EXTERNAL_VIS NS_VISIBILITY_DEFAULT + +/** + * Mark a function as using a potentially non-standard function calling + * convention. This can be used on functions that are called very + * frequently, to reduce the overhead of the function call. It is still worth + * using the macro for C++ functions which take no parameters since it allows + * passing |this| in a register. + * + * - Do not use this on any scriptable interface method since xptcall won't be + * aware of the different calling convention. + * - This must appear on the declaration, not the definition. + * - Adding this to a public function _will_ break binary compatibility. + * - This may be used on virtual functions but you must ensure it is applied + * to all implementations - the compiler will _not_ warn but it will crash. + * - This has no effect for functions which take a variable number of + * arguments. + * - __fastcall on windows should not be applied to class + * constructors/destructors - use the NS_CONSTRUCTOR_FASTCALL macro for + * constructors/destructors. + * + * Examples: int NS_FASTCALL func1(char *foo); + * NS_HIDDEN_(int) NS_FASTCALL func2(char *foo); + */ + +#if defined(__i386__) && defined(__GNUC__) +#define NS_FASTCALL __attribute__ ((regparm (3), stdcall)) +#define NS_CONSTRUCTOR_FASTCALL __attribute__ ((regparm (3), stdcall)) +#elif defined(XP_WIN) && !defined(_WIN64) +#define NS_FASTCALL __fastcall +#define NS_CONSTRUCTOR_FASTCALL +#else +#define NS_FASTCALL +#define NS_CONSTRUCTOR_FASTCALL +#endif + +#ifdef XP_WIN + +#define NS_IMPORT __declspec(dllimport) +#define NS_IMPORT_(type) __declspec(dllimport) type __stdcall +#define NS_EXPORT __declspec(dllexport) +#define NS_EXPORT_(type) __declspec(dllexport) type __stdcall +#define NS_IMETHOD_(type) virtual type __stdcall +#define NS_IMETHODIMP_(type) type __stdcall +#define NS_METHOD_(type) type __stdcall +#define NS_CALLBACK_(_type, _name) _type (__stdcall * _name) +#ifndef _WIN64 +// Win64 has only one calling convention. __stdcall will be ignored by the compiler. +#define NS_STDCALL __stdcall +#define NS_HAVE_STDCALL +#else +#define NS_STDCALL +#endif +#define NS_FROZENCALL __cdecl + +#else + +#define NS_IMPORT NS_EXTERNAL_VIS +#define NS_IMPORT_(type) NS_EXTERNAL_VIS_(type) +#define NS_EXPORT NS_EXTERNAL_VIS +#define NS_EXPORT_(type) NS_EXTERNAL_VIS_(type) +#define NS_IMETHOD_(type) virtual type +#define NS_IMETHODIMP_(type) type +#define NS_METHOD_(type) type +#define NS_CALLBACK_(_type, _name) _type (* _name) +#define NS_STDCALL +#define NS_FROZENCALL + +#endif + +/** + * Macro for creating typedefs for pointer-to-member types which are + * declared with stdcall. It is important to use this for any type which is + * declared as stdcall (i.e. NS_IMETHOD). For example, instead of writing: + * + * typedef nsresult (nsIFoo::*someType)(nsISupports* arg); + * + * you should write: + * + * typedef + * NS_STDCALL_FUNCPROTO(nsresult, someType, nsIFoo, typeFunc, (nsISupports*)); + * + * where nsIFoo::typeFunc is any method declared as + * NS_IMETHOD typeFunc(nsISupports*); + * + * XXX this can be simplified to always use the non-typeof implementation + * when http://gcc.gnu.org/bugzilla/show_bug.cgi?id=11893 is fixed. + */ + +#ifdef __GNUC__ +#define NS_STDCALL_FUNCPROTO(ret, name, class, func, args) \ + typeof(&class::func) name +#else +#define NS_STDCALL_FUNCPROTO(ret, name, class, func, args) \ + ret (NS_STDCALL class::*name) args +#endif + +/** + * Deprecated declarations. + */ +#ifdef __GNUC__ +# define MOZ_DEPRECATED __attribute__((deprecated)) +#elif defined(_MSC_VER) +# define MOZ_DEPRECATED __declspec(deprecated) +#else +# define MOZ_DEPRECATED +#endif + +/** + * Generic API modifiers which return the standard XPCOM nsresult type + * + * - NS_IMETHOD: use for in-class declarations and definitions. + * - NS_IMETHODIMP: use for out-of-class definitions. + * - NS_METHOD: usually used in conjunction with NS_CALLBACK. + * - NS_CALLBACK: used in some legacy situations. Best avoided. + */ +#define NS_IMETHOD NS_IMETHOD_(nsresult) +#define NS_IMETHODIMP NS_IMETHODIMP_(nsresult) +#define NS_METHOD NS_METHOD_(nsresult) +#define NS_CALLBACK(_name) NS_CALLBACK_(nsresult, _name) + +/** + * Import/Export macros for XPCOM APIs + */ + +#ifdef __cplusplus +#define NS_EXTERN_C extern "C" +#else +#define NS_EXTERN_C +#endif + +#define EXPORT_XPCOM_API(type) NS_EXTERN_C NS_EXPORT type NS_FROZENCALL +#define IMPORT_XPCOM_API(type) NS_EXTERN_C NS_IMPORT type NS_FROZENCALL +#define GLUE_XPCOM_API(type) NS_EXTERN_C NS_HIDDEN_(type) NS_FROZENCALL + +#ifdef IMPL_LIBXUL +#define XPCOM_API(type) EXPORT_XPCOM_API(type) +#elif defined(XPCOM_GLUE) +#define XPCOM_API(type) GLUE_XPCOM_API(type) +#else +#define XPCOM_API(type) IMPORT_XPCOM_API(type) +#endif + +#ifdef MOZILLA_INTERNAL_API + /* + The frozen string API has different definitions of nsAC?String + classes than the internal API. On systems that explicitly declare + dllexport symbols this is not a problem, but on ELF systems + internal symbols can accidentally "shine through"; we rename the + internal classes to avoid symbol conflicts. + */ +# define nsAString nsAString_internal +# define nsACString nsACString_internal +#endif + +#if (defined(DEBUG) || defined(FORCE_BUILD_REFCNT_LOGGING)) +/* Make refcnt logging part of the build. This doesn't mean that + * actual logging will occur (that requires a separate enable; see + * nsTraceRefcnt and nsISupportsImpl.h for more information). */ +#define NS_BUILD_REFCNT_LOGGING +#endif + +/* If NO_BUILD_REFCNT_LOGGING is defined then disable refcnt logging + * in the build. This overrides FORCE_BUILD_REFCNT_LOGGING. */ +#if defined(NO_BUILD_REFCNT_LOGGING) +#undef NS_BUILD_REFCNT_LOGGING +#endif + +/* If a program allocates memory for the lifetime of the app, it doesn't make + * sense to touch memory pages and free that memory at shutdown, + * unless we are running leak stats. + */ +#if defined(NS_BUILD_REFCNT_LOGGING) || defined(MOZ_VALGRIND) || defined(MOZ_ASAN) +#define NS_FREE_PERMANENT_DATA +#endif + +/** + * NS_NO_VTABLE is emitted by xpidl in interface declarations whenever + * xpidl can determine that the interface can't contain a constructor. + * This results in some space savings and possible runtime savings - + * see bug 49416. We undefine it first, as xpidl-generated headers + * define it for IDL uses that don't include this file. + */ +#ifdef NS_NO_VTABLE +#undef NS_NO_VTABLE +#endif +#if defined(_MSC_VER) +#define NS_NO_VTABLE __declspec(novtable) +#else +#define NS_NO_VTABLE +#endif + + +/** + * Generic XPCOM result data type + */ +#include "nsError.h" + +typedef MozRefCountType nsrefcnt; + +/* + * Use these macros to do 64bit safe pointer conversions. + */ + +#define NS_PTR_TO_INT32(x) ((int32_t)(intptr_t)(x)) +#define NS_PTR_TO_UINT32(x) ((uint32_t)(intptr_t)(x)) +#define NS_INT32_TO_PTR(x) ((void*)(intptr_t)(x)) + +/* + * Use NS_STRINGIFY to form a string literal from the value of a macro. + */ +#define NS_STRINGIFY_HELPER(x_) #x_ +#define NS_STRINGIFY(x_) NS_STRINGIFY_HELPER(x_) + +/* + * If we're being linked as standalone glue, we don't want a dynamic + * dependency on NSPR libs, so we skip the debug thread-safety + * checks, and we cannot use the THREADSAFE_ISUPPORTS macros. + */ +#if defined(XPCOM_GLUE) && !defined(XPCOM_GLUE_USE_NSPR) +#define XPCOM_GLUE_AVOID_NSPR +#endif + +/* + * SEH exception macros. + */ +#ifdef HAVE_SEH_EXCEPTIONS +#define MOZ_SEH_TRY __try +#define MOZ_SEH_EXCEPT(expr) __except(expr) +#else +#define MOZ_SEH_TRY if(true) +#define MOZ_SEH_EXCEPT(expr) else +#endif + +#endif /* nscore_h___ */ diff --git a/xpcom/base/nsrootidl.idl b/xpcom/base/nsrootidl.idl new file mode 100644 index 000000000..55795b7bc --- /dev/null +++ b/xpcom/base/nsrootidl.idl @@ -0,0 +1,97 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Root idl declarations to be used by all. + */ + +%{C++ + +#include "nscore.h" +typedef int64_t PRTime; + +/* + * Forward declarations for new string types + */ +class nsAString; +class nsACString; + +/* + * Start commenting out the C++ versions of the below in the output header + */ +#if 0 +%} + +typedef boolean bool ; +typedef octet uint8_t ; +typedef unsigned short uint16_t ; +typedef unsigned short char16_t; +typedef unsigned long uint32_t ; +typedef unsigned long long uint64_t ; +typedef long long PRTime ; +typedef short int16_t ; +typedef long int32_t ; +typedef long long int64_t ; + +typedef unsigned long nsrefcnt ; +typedef unsigned long nsresult ; + +// XXX need this built into xpidl compiler so that it's really size_t or size_t +// and it's scriptable: +typedef unsigned long size_t; + +[ptr] native voidPtr(void); +[ptr] native charPtr(char); +[ptr] native unicharPtr(char16_t); + +[ref, nsid] native nsIDRef(nsID); +[ref, nsid] native nsIIDRef(nsIID); +[ref, nsid] native nsCIDRef(nsCID); + +[ptr, nsid] native nsIDPtr(nsID); +[ptr, nsid] native nsIIDPtr(nsIID); +[ptr, nsid] native nsCIDPtr(nsCID); + +// NOTE: Be careful in using the following 3 types. The *Ref and *Ptr variants +// are more commonly used (and better supported). Those variants require +// nsMemory alloc'd copies when used as 'out' params while these types do not. +// However, currently these types can not be used for 'in' params. And, methods +// that use them as 'out' params *must* be declared [notxpcom] (with an explicit +// return type of nsresult). This makes such methods implicitly not scriptable. +// Use of these types in methods without a [notxpcom] declaration will cause +// the xpidl compiler to raise an error. +// See: http://bugzilla.mozilla.org/show_bug.cgi?id=93792 + +[nsid] native nsIID(nsIID); +[nsid] native nsID(nsID); +[nsid] native nsCID(nsCID); + +[ptr] native nsQIResult(void); + +[ref, domstring] native DOMString(ignored); +[ref, domstring] native DOMStringRef(ignored); +[ptr, domstring] native DOMStringPtr(ignored); + +[ref, utf8string] native AUTF8String(ignored); +[ref, utf8string] native AUTF8StringRef(ignored); +[ptr, utf8string] native AUTF8StringPtr(ignored); + +[ref, cstring] native ACString(ignored); +[ref, cstring] native ACStringRef(ignored); +[ptr, cstring] native ACStringPtr(ignored); + +[ref, astring] native AString(ignored); +[ref, astring] native AStringRef(ignored); +[ptr, astring] native AStringPtr(ignored); + +[ref, jsval] native jsval(jsval); + native jsid(jsid); + +%{C++ +/* + * End commenting out the C++ versions of the above in the output header + */ +#endif +%} |