diff options
Diffstat (limited to '')
163 files changed, 39305 insertions, 0 deletions
diff --git a/xpcom/base/AppShutdown.cpp b/xpcom/base/AppShutdown.cpp new file mode 100644 index 0000000000..c68dedef31 --- /dev/null +++ b/xpcom/base/AppShutdown.cpp @@ -0,0 +1,469 @@ +/* -*- 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 "ShutdownPhase.h" +#ifdef XP_WIN +# include <windows.h> +# include "mozilla/PreXULSkeletonUI.h" +#else +# include <unistd.h> +#endif + +#include "ProfilerControl.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/CmdLineAndEnvUtils.h" +#include "mozilla/PoisonIOInterposer.h" +#include "mozilla/Printf.h" +#include "mozilla/scache/StartupCache.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "mozilla/StartupTimeline.h" +#include "mozilla/StaticPrefs_toolkit.h" +#include "mozilla/LateWriteChecks.h" +#include "mozilla/Services.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsAppRunner.h" +#include "nsDirectoryServiceUtils.h" +#include "nsExceptionHandler.h" +#include "nsICertStorage.h" +#include "nsThreadUtils.h" + +#include "AppShutdown.h" + +// TODO: understand why on Android we cannot include this and if we should +#ifndef ANDROID +# include "nsTerminator.h" +#endif +#include "prenv.h" + +#ifdef MOZ_BACKGROUNDTASKS +# include "mozilla/BackgroundTasks.h" +#endif + +namespace mozilla { + +const char* sPhaseObserverKeys[] = { + nullptr, // NotInShutdown + "quit-application", // AppShutdownConfirmed + "profile-change-net-teardown", // AppShutdownNetTeardown + "profile-change-teardown", // AppShutdownTeardown + "profile-before-change", // AppShutdown + "profile-before-change-qm", // AppShutdownQM + "profile-before-change-telemetry", // AppShutdownTelemetry + "xpcom-will-shutdown", // XPCOMWillShutdown + "xpcom-shutdown", // XPCOMShutdown + "xpcom-shutdown-threads", // XPCOMShutdownThreads + nullptr, // XPCOMShutdownFinal + nullptr // CCPostLastCycleCollection +}; + +static_assert(sizeof(sPhaseObserverKeys) / sizeof(sPhaseObserverKeys[0]) == + (size_t)ShutdownPhase::ShutdownPhase_Length); + +const char* sPhaseReadableNames[] = {"NotInShutdown", + "AppShutdownConfirmed", + "AppShutdownNetTeardown", + "AppShutdownTeardown", + "AppShutdown", + "AppShutdownQM", + "AppShutdownTelemetry", + "XPCOMWillShutdown", + "XPCOMShutdown", + "XPCOMShutdownThreads", + "XPCOMShutdownFinal", + "CCPostLastCycleCollection"}; + +static_assert(sizeof(sPhaseReadableNames) / sizeof(sPhaseReadableNames[0]) == + (size_t)ShutdownPhase::ShutdownPhase_Length); + +#ifndef ANDROID +static nsTerminator* sTerminator = nullptr; +#endif + +static ShutdownPhase sFastShutdownPhase = ShutdownPhase::NotInShutdown; +static ShutdownPhase sLateWriteChecksPhase = ShutdownPhase::NotInShutdown; +static AppShutdownMode sShutdownMode = AppShutdownMode::Normal; +static Atomic<ShutdownPhase> sCurrentShutdownPhase( + ShutdownPhase::NotInShutdown); +static int sExitCode = 0; + +// These environment variable strings are all deliberately copied and leaked +// due to requirements of PR_SetEnv and similar. +static char* sSavedXulAppFile = nullptr; +#ifdef XP_WIN +static wchar_t* sSavedProfDEnvVar = nullptr; +static wchar_t* sSavedProfLDEnvVar = nullptr; +#else +static char* sSavedProfDEnvVar = nullptr; +static char* sSavedProfLDEnvVar = nullptr; +#endif + +ShutdownPhase GetShutdownPhaseFromPrefValue(int32_t aPrefValue) { + switch (aPrefValue) { + case 1: + return ShutdownPhase::CCPostLastCycleCollection; + case 2: + return ShutdownPhase::XPCOMShutdownThreads; + case 3: + return ShutdownPhase::XPCOMShutdown; + // NOTE: the remaining values from the ShutdownPhase enum will be added + // when we're at least reasonably confident that the world won't come + // crashing down if we do a fast shutdown at that point. + } + return ShutdownPhase::NotInShutdown; +} + +ShutdownPhase AppShutdown::GetCurrentShutdownPhase() { + return sCurrentShutdownPhase; +} + +bool AppShutdown::IsInOrBeyond(ShutdownPhase aPhase) { + return (sCurrentShutdownPhase >= aPhase); +} + +int AppShutdown::GetExitCode() { return sExitCode; } + +void AppShutdown::SaveEnvVarsForPotentialRestart() { + const char* s = PR_GetEnv("XUL_APP_FILE"); + if (s) { + sSavedXulAppFile = Smprintf("%s=%s", "XUL_APP_FILE", s).release(); + MOZ_LSAN_INTENTIONALLY_LEAK_OBJECT(sSavedXulAppFile); + } +} + +const char* AppShutdown::GetObserverKey(ShutdownPhase aPhase) { + return sPhaseObserverKeys[static_cast<std::underlying_type_t<ShutdownPhase>>( + aPhase)]; +} + +const char* AppShutdown::GetShutdownPhaseName(ShutdownPhase aPhase) { + return sPhaseReadableNames[static_cast<std::underlying_type_t<ShutdownPhase>>( + aPhase)]; +} + +void AppShutdown::MaybeDoRestart() { + if (sShutdownMode == AppShutdownMode::Restart) { + StopLateWriteChecks(); + + // Since we'll be launching our child while we're still alive, make sure + // we've unlocked the profile first, otherwise the child could hit its + // profile lock check before we've exited and thus released our lock. + UnlockProfile(); + + if (sSavedXulAppFile) { + PR_SetEnv(sSavedXulAppFile); + } + +#ifdef XP_WIN + if (sSavedProfDEnvVar && !EnvHasValue("XRE_PROFILE_PATH")) { + SetEnvironmentVariableW(L"XRE_PROFILE_PATH", sSavedProfDEnvVar); + } + if (sSavedProfLDEnvVar && !EnvHasValue("XRE_PROFILE_LOCAL_PATH")) { + SetEnvironmentVariableW(L"XRE_PROFILE_LOCAL_PATH", sSavedProfLDEnvVar); + } + Unused << NotePreXULSkeletonUIRestarting(); +#else + if (sSavedProfDEnvVar && !EnvHasValue("XRE_PROFILE_PATH")) { + PR_SetEnv(sSavedProfDEnvVar); + } + if (sSavedProfLDEnvVar && !EnvHasValue("XRE_PROFILE_LOCAL_PATH")) { + PR_SetEnv(sSavedProfLDEnvVar); + } +#endif + + LaunchChild(true); + } +} + +#ifdef XP_WIN +wchar_t* CopyPathIntoNewWCString(nsIFile* aFile) { + wchar_t* result = nullptr; + nsAutoString resStr; + aFile->GetPath(resStr); + if (resStr.Length() > 0) { + result = (wchar_t*)malloc((resStr.Length() + 1) * sizeof(wchar_t)); + if (result) { + wcscpy(result, resStr.get()); + result[resStr.Length()] = 0; + } + } + + return result; +} +#endif + +void AppShutdown::Init(AppShutdownMode aMode, int aExitCode, + AppShutdownReason aReason) { + if (sShutdownMode == AppShutdownMode::Normal) { + sShutdownMode = aMode; + } + AppShutdown::AnnotateShutdownReason(aReason); + + sExitCode = aExitCode; + +#ifndef ANDROID + sTerminator = new nsTerminator(); +#endif + + // Late-write checks needs to find the profile directory, so it has to + // be initialized before services::Shutdown or (because of + // xpcshell tests replacing the service) modules being unloaded. + InitLateWriteChecks(); + + int32_t fastShutdownPref = StaticPrefs::toolkit_shutdown_fastShutdownStage(); + sFastShutdownPhase = GetShutdownPhaseFromPrefValue(fastShutdownPref); + int32_t lateWriteChecksPref = + StaticPrefs::toolkit_shutdown_lateWriteChecksStage(); + sLateWriteChecksPhase = GetShutdownPhaseFromPrefValue(lateWriteChecksPref); + + // Very early shutdowns can happen before the startup cache is even + // initialized; don't bother initializing it during shutdown. + if (auto* cache = scache::StartupCache::GetSingletonNoInit()) { + cache->MaybeInitShutdownWrite(); + } +} + +void AppShutdown::MaybeFastShutdown(ShutdownPhase aPhase) { + // For writes which we want to ensure are recorded, we don't want to trip + // the late write checking code. Anything that writes to disk and which + // we don't want to skip should be listed out explicitly in this section. + if (aPhase == sFastShutdownPhase || aPhase == sLateWriteChecksPhase) { + if (auto* cache = scache::StartupCache::GetSingletonNoInit()) { + cache->EnsureShutdownWriteComplete(); + } + + nsresult rv; + nsCOMPtr<nsICertStorage> certStorage = + do_GetService("@mozilla.org/security/certstorage;1", &rv); + if (NS_SUCCEEDED(rv)) { + SpinEventLoopUntil("AppShutdown::MaybeFastShutdown"_ns, [&]() { + int32_t remainingOps; + nsresult rv = certStorage->GetRemainingOperationCount(&remainingOps); + NS_ASSERTION(NS_SUCCEEDED(rv), + "nsICertStorage::getRemainingOperationCount failed during " + "shutdown"); + return NS_FAILED(rv) || remainingOps <= 0; + }); + } + } + if (aPhase == sFastShutdownPhase) { + StopLateWriteChecks(); + RecordShutdownEndTimeStamp(); + MaybeDoRestart(); + + profiler_shutdown(IsFastShutdown::Yes); + + DoImmediateExit(sExitCode); + } else if (aPhase == sLateWriteChecksPhase) { +#ifdef XP_MACOSX + OnlyReportDirtyWrites(); +#endif /* XP_MACOSX */ + BeginLateWriteChecks(); + } +} + +void AppShutdown::OnShutdownConfirmed() { + // If we're restarting, we need to save environment variables correctly + // while everything is still alive to do so. + if (sShutdownMode == AppShutdownMode::Restart) { + nsCOMPtr<nsIFile> profD; + nsCOMPtr<nsIFile> profLD; + NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(profD)); + NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR, + getter_AddRefs(profLD)); +#ifdef XP_WIN + sSavedProfDEnvVar = CopyPathIntoNewWCString(profD); + sSavedProfLDEnvVar = CopyPathIntoNewWCString(profLD); +#else + nsAutoCString profDStr; + profD->GetNativePath(profDStr); + sSavedProfDEnvVar = + Smprintf("XRE_PROFILE_PATH=%s", profDStr.get()).release(); + nsAutoCString profLDStr; + profLD->GetNativePath(profLDStr); + sSavedProfLDEnvVar = + Smprintf("XRE_PROFILE_LOCAL_PATH=%s", profLDStr.get()).release(); +#endif + MOZ_LSAN_INTENTIONALLY_LEAK_OBJECT(sSavedProfDEnvVar); + MOZ_LSAN_INTENTIONALLY_LEAK_OBJECT(sSavedProfLDEnvVar); + } +} + +void AppShutdown::DoImmediateExit(int aExitCode) { +#ifdef XP_WIN + HANDLE process = ::GetCurrentProcess(); + if (::TerminateProcess(process, aExitCode)) { + ::WaitForSingleObject(process, INFINITE); + } + MOZ_CRASH("TerminateProcess failed."); +#else + _exit(aExitCode); +#endif +} + +bool AppShutdown::IsRestarting() { + return sShutdownMode == AppShutdownMode::Restart; +} + +void AppShutdown::AnnotateShutdownReason(AppShutdownReason aReason) { + auto key = CrashReporter::Annotation::ShutdownReason; + nsCString reasonStr; + switch (aReason) { + case AppShutdownReason::AppClose: + reasonStr = "AppClose"_ns; + break; + case AppShutdownReason::AppRestart: + reasonStr = "AppRestart"_ns; + break; + case AppShutdownReason::OSForceClose: + reasonStr = "OSForceClose"_ns; + break; + case AppShutdownReason::OSSessionEnd: + reasonStr = "OSSessionEnd"_ns; + break; + case AppShutdownReason::OSShutdown: + reasonStr = "OSShutdown"_ns; + break; + case AppShutdownReason::WinUnexpectedMozQuit: + reasonStr = "WinUnexpectedMozQuit"_ns; + break; + default: + MOZ_ASSERT_UNREACHABLE("We should know the given reason for shutdown."); + reasonStr = "Unknown"_ns; + break; + } + CrashReporter::AnnotateCrashReport(key, reasonStr); +} + +#ifdef DEBUG +static bool sNotifyingShutdownObservers = false; +static bool sAdvancingShutdownPhase = false; + +bool AppShutdown::IsNoOrLegalShutdownTopic(const char* aTopic) { + if (!XRE_IsParentProcess()) { + // Until we know what to do with AppShutdown for child processes, + // we ignore them for now. See bug 1697745. + return true; + } + ShutdownPhase phase = GetShutdownPhaseFromTopic(aTopic); + return phase == ShutdownPhase::NotInShutdown || + (sNotifyingShutdownObservers && phase == sCurrentShutdownPhase); +} +#endif + +void AppShutdown::AdvanceShutdownPhaseInternal( + ShutdownPhase aPhase, bool doNotify, const char16_t* aNotificationData, + const nsCOMPtr<nsISupports>& aNotificationSubject) { + AssertIsOnMainThread(); +#ifdef DEBUG + // Prevent us from re-entrance + MOZ_ASSERT(!sAdvancingShutdownPhase); + sAdvancingShutdownPhase = true; + auto exit = MakeScopeExit([] { sAdvancingShutdownPhase = false; }); +#endif + + // We ensure that we can move only forward. We cannot + // MOZ_ASSERT here as there are some tests that fire + // notifications out of shutdown order. + // See for example test_sss_sanitizeOnShutdown.js + if (sCurrentShutdownPhase >= aPhase) { + return; + } + + nsCOMPtr<nsIThread> thread = do_GetCurrentThread(); + + // AppShutdownConfirmed is special in some ways as + // - we can be called on top of a nested event loop (and it is the phase for + // which SpinEventLoopUntilOrQuit breaks, so we want to return soon) + // - we can be called from a sync marionette function that wants immediate + // feedback, too + // - in general, AppShutdownConfirmed will fire the "quit-application" + // notification which in turn will cause an event to be dispatched that + // runs all the rest of our shutdown sequence which we do not want to be + // processed on top of the running event. + // Thus we never do any NS_ProcessPendingEvents for it. + bool mayProcessPending = (aPhase > ShutdownPhase::AppShutdownConfirmed); + + // Give runnables dispatched between two calls to AdvanceShutdownPhase + // a chance to run before actually advancing the phase. As we excluded + // AppShutdownConfirmed above we can be sure that the processing is + // covered by the terminator's timer of the previous phase during normal + // shutdown (except out-of-order calls from some test). + // Note that this affects only main thread runnables, such that the correct + // way of ensuring shutdown processing remains to have an async shutdown + // blocker. + if (mayProcessPending && thread) { + NS_ProcessPendingEvents(thread); + } + + // From now on any IsInOrBeyond checks will find the new phase set. + sCurrentShutdownPhase = aPhase; + +#ifndef ANDROID + if (sTerminator) { + sTerminator->AdvancePhase(aPhase); + } +#endif + + AppShutdown::MaybeFastShutdown(aPhase); + + // This will null out the gathered pointers for this phase synchronously. + // Note that we keep the old order here to avoid breakage, so be aware that + // the notifications fired below will find these already cleared in case + // you expected the opposite. + mozilla::KillClearOnShutdown(aPhase); + + // Empty our MT event queue to process any side effects thereof. + if (mayProcessPending && thread) { + NS_ProcessPendingEvents(thread); + } + + if (doNotify) { + const char* aTopic = AppShutdown::GetObserverKey(aPhase); + if (aTopic) { + nsCOMPtr<nsIObserverService> obsService = + mozilla::services::GetObserverService(); + if (obsService) { +#ifdef DEBUG + sNotifyingShutdownObservers = true; + auto reset = MakeScopeExit([] { sNotifyingShutdownObservers = false; }); +#endif + obsService->NotifyObservers(aNotificationSubject, aTopic, + aNotificationData); + // Empty our MT event queue again after the notification has finished + if (mayProcessPending && thread) { + NS_ProcessPendingEvents(thread); + } + } + } + } +} + +/** + * XXX: Before tackling bug 1697745 we need the + * possibility to advance the phase without notification + * in the content process. + */ +void AppShutdown::AdvanceShutdownPhaseWithoutNotify(ShutdownPhase aPhase) { + AdvanceShutdownPhaseInternal(aPhase, /* doNotify */ false, nullptr, nullptr); +} + +void AppShutdown::AdvanceShutdownPhase( + ShutdownPhase aPhase, const char16_t* aNotificationData, + const nsCOMPtr<nsISupports>& aNotificationSubject) { + AdvanceShutdownPhaseInternal(aPhase, /* doNotify */ true, aNotificationData, + aNotificationSubject); +} + +ShutdownPhase AppShutdown::GetShutdownPhaseFromTopic(const char* aTopic) { + for (size_t i = 0; i < ArrayLength(sPhaseObserverKeys); ++i) { + if (sPhaseObserverKeys[i] && !strcmp(sPhaseObserverKeys[i], aTopic)) { + return static_cast<ShutdownPhase>(i); + } + } + return ShutdownPhase::NotInShutdown; +} + +} // namespace mozilla diff --git a/xpcom/base/AppShutdown.h b/xpcom/base/AppShutdown.h new file mode 100644 index 0000000000..6056338900 --- /dev/null +++ b/xpcom/base/AppShutdown.h @@ -0,0 +1,152 @@ +/* -*- 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 AppShutdown_h +#define AppShutdown_h + +#include <type_traits> +#include "nsCOMPtr.h" +#include "nsISupports.h" +#include "ShutdownPhase.h" + +namespace mozilla { + +enum class AppShutdownMode { + Normal, + Restart, +}; + +enum class AppShutdownReason { + // No reason. + Unknown, + // Normal application shutdown. + AppClose, + // The application wants to restart. + AppRestart, + // The OS is force closing us. + OSForceClose, + // The user is logging off from the OS session, the system may stay alive. + OSSessionEnd, + // The system is shutting down (and maybe restarting). + OSShutdown, + // We unexpectedly received MOZ_WM_APP_QUIT, see bug 1827807. + WinUnexpectedMozQuit, +}; + +class AppShutdown { + public: + static ShutdownPhase GetCurrentShutdownPhase(); + static bool IsInOrBeyond(ShutdownPhase aPhase); + + /** + * Returns the current exit code that the process will be terminated with. + */ + static int GetExitCode(); + + /** + * Save environment variables that we might need if the app initiates a + * restart later in its lifecycle. + */ + static void SaveEnvVarsForPotentialRestart(); + + /** + * Init the shutdown with the requested shutdown mode, exit code and optional + * a reason (if missing it will be derived from aMode). + */ + static void Init(AppShutdownMode aMode, int aExitCode, + AppShutdownReason aReason); + + /** + * Confirm that we are in fact going to be shutting down. + */ + static void OnShutdownConfirmed(); + + /** + * If we've attempted to initiate a restart, this call will set up the + * necessary environment variables and launch the new process. + */ + static void MaybeDoRestart(); + + /** + * The _exit() call is not a safe way to terminate your own process on + * Windows, because _exit runs DLL detach callbacks which run static + * destructors for xul.dll. + * + * This method terminates the current process without those issues. + * + * Optionally a custom exit code can be supplied. + */ + static void DoImmediateExit(int aExitCode = 0); + + /** + * True if the application is currently attempting to shut down in order to + * restart. + */ + static bool IsRestarting(); + + /** + * Wrapper for shutdown notifications that informs the terminator before + * we notify other observers. Calls MaybeFastShutdown. + */ + static void AdvanceShutdownPhase( + ShutdownPhase aPhase, const char16_t* aNotificationData = nullptr, + const nsCOMPtr<nsISupports>& aNotificationSubject = + nsCOMPtr<nsISupports>(nullptr)); + + /** + * XXX: Before tackling bug 1697745 we need the + * possibility to advance the phase without notification + * in the content process. + */ + static void AdvanceShutdownPhaseWithoutNotify(ShutdownPhase aPhase); + + /** + * Map shutdown phase to observer key + */ + static const char* GetObserverKey(ShutdownPhase aPhase); + + /** + * Map shutdown phase to readable name + */ + static const char* GetShutdownPhaseName(ShutdownPhase aPhase); + + /** + * Map observer topic key to shutdown phase + */ + static ShutdownPhase GetShutdownPhaseFromTopic(const char* aTopic); + +#ifdef DEBUG + /** + * Check, if we are allowed to send a shutdown notification. + * Shutdown specific topics are only allowed during calls to + * AdvanceShutdownPhase itself. + */ + static bool IsNoOrLegalShutdownTopic(const char* aTopic); +#endif + + private: + /** + * Set the shutdown reason annotation. + */ + static void AnnotateShutdownReason(AppShutdownReason aReason); + + /** + * This will perform a fast shutdown via _exit(0) or similar if the user's + * prefs are configured to do so at this phase. + */ + static void MaybeFastShutdown(ShutdownPhase aPhase); + + /** + * Internal helper function, uses MaybeFastShutdown. + */ + static void AdvanceShutdownPhaseInternal( + ShutdownPhase aPhase, bool doNotify, const char16_t* aNotificationData, + const nsCOMPtr<nsISupports>& aNotificationSubject); +}; + +} // namespace mozilla + +#endif // AppShutdown_h diff --git a/xpcom/base/AutoRestore.h b/xpcom/base/AutoRestore.h new file mode 100644 index 0000000000..fc585da14b --- /dev/null +++ b/xpcom/base/AutoRestore.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/. */ + +/* functions for restoring saved values at the end of a C++ scope */ + +#ifndef mozilla_AutoRestore_h_ +#define mozilla_AutoRestore_h_ + +#include "mozilla/Attributes.h" // MOZ_STACK_CLASS + +namespace mozilla { + +/** + * Save the current value of a variable and restore it when the object + * goes out of scope. For example: + * { + * AutoRestore<bool> savePainting(mIsPainting); + * mIsPainting = true; + * + * // ... your code here ... + * + * // mIsPainting is reset to its old value at the end of this block + * } + */ +template <class T> +class MOZ_RAII AutoRestore { + private: + T& mLocation; + T mValue; + + public: + explicit AutoRestore(T& aValue) : mLocation(aValue), mValue(aValue) {} + ~AutoRestore() { mLocation = mValue; } + T SavedValue() const { return mValue; } +}; + +} // namespace mozilla + +#endif /* !defined(mozilla_AutoRestore_h_) */ diff --git a/xpcom/base/AvailableMemoryTracker.cpp b/xpcom/base/AvailableMemoryTracker.cpp new file mode 100644 index 0000000000..a14c43bcbc --- /dev/null +++ b/xpcom/base/AvailableMemoryTracker.cpp @@ -0,0 +1,189 @@ +/* -*- 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 <windows.h> +# include "nsIMemoryReporter.h" +#endif + +#include "nsIObserver.h" +#include "nsIObserverService.h" +#include "nsIRunnable.h" +#include "nsISupports.h" +#include "nsThreadUtils.h" +#include "nsXULAppAPI.h" + +#include "mozilla/Mutex.h" +#include "mozilla/ResultExtensions.h" +#include "mozilla/Services.h" + +#if defined(MOZ_MEMORY) +# include "mozmemory.h" +#endif // MOZ_MEMORY + +using namespace mozilla; + +Atomic<uint32_t, MemoryOrdering::Relaxed> sNumLowPhysicalMemEvents; + +namespace { + +#if defined(XP_WIN) + +# if defined(__MINGW32__) +// Definitions for heap optimization that require the Windows SDK to target the +// Windows 8.1 Update +static const HEAP_INFORMATION_CLASS HeapOptimizeResources = + static_cast<HEAP_INFORMATION_CLASS>(3); + +static const DWORD HEAP_OPTIMIZE_RESOURCES_CURRENT_VERSION = 1; + +typedef struct _HEAP_OPTIMIZE_RESOURCES_INFORMATION { + DWORD Version; + DWORD Flags; +} HEAP_OPTIMIZE_RESOURCES_INFORMATION, *PHEAP_OPTIMIZE_RESOURCES_INFORMATION; +# endif + +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 { + // clang-format off + 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 when a windows low memory resource notification is signaled. 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."); + // clang-format on + + return NS_OK; + } +}; + +NS_IMPL_ISUPPORTS(LowEventsReporter, nsIMemoryReporter) + +#endif // 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 Runnable { + ~nsJemallocFreeDirtyPagesRunnable() = default; + +#if defined(XP_WIN) + void OptimizeSystemHeap(); +#endif + + public: + NS_DECL_NSIRUNNABLE + + nsJemallocFreeDirtyPagesRunnable() + : Runnable("nsJemallocFreeDirtyPagesRunnable") {} +}; + +NS_IMETHODIMP +nsJemallocFreeDirtyPagesRunnable::Run() { + MOZ_ASSERT(NS_IsMainThread()); + +#if defined(MOZ_MEMORY) + jemalloc_free_dirty_pages(); +#endif + +#if defined(XP_WIN) + OptimizeSystemHeap(); +#endif + + return NS_OK; +} + +#if defined(XP_WIN) +void nsJemallocFreeDirtyPagesRunnable::OptimizeSystemHeap() { + HEAP_OPTIMIZE_RESOURCES_INFORMATION heapOptInfo = { + HEAP_OPTIMIZE_RESOURCES_CURRENT_VERSION}; + + ::HeapSetInformation(nullptr, HeapOptimizeResources, &heapOptInfo, + sizeof(heapOptInfo)); +} +#endif // defined(XP_WIN) + +/** + * 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() = default; + + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + void Init(); +}; + +NS_IMPL_ISUPPORTS(nsMemoryPressureWatcher, nsIObserver) + +/** + * 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); + } +} + +/** + * 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"); + + nsCOMPtr<nsIRunnable> runnable = new nsJemallocFreeDirtyPagesRunnable(); + + NS_DispatchToMainThread(runnable); + + return NS_OK; +} + +} // namespace + +namespace mozilla { +namespace AvailableMemoryTracker { + +void Init() { + // The watchers are held alive by the observer service. + RefPtr<nsMemoryPressureWatcher> watcher = new nsMemoryPressureWatcher(); + watcher->Init(); + +#if defined(XP_WIN) + RegisterLowMemoryEventsPhysicalDistinguishedAmount( + LowMemoryEventsPhysicalDistinguishedAmount); +#endif // defined(XP_WIN) +} + +} // namespace AvailableMemoryTracker +} // namespace mozilla diff --git a/xpcom/base/AvailableMemoryTracker.h b/xpcom/base/AvailableMemoryTracker.h new file mode 100644 index 0000000000..3d2a048cb3 --- /dev/null +++ b/xpcom/base/AvailableMemoryTracker.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_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() requires the observer service to be already available so cannot be +// called too early during initialization. + +void Init(); + +} // namespace AvailableMemoryTracker +} // namespace mozilla + +#endif // ifndef mozilla_AvailableMemoryTracker_h diff --git a/xpcom/base/AvailableMemoryWatcher.cpp b/xpcom/base/AvailableMemoryWatcher.cpp new file mode 100644 index 0000000000..a2de368b01 --- /dev/null +++ b/xpcom/base/AvailableMemoryWatcher.cpp @@ -0,0 +1,186 @@ +/* -*- 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 "AvailableMemoryWatcher.h" + +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/RefPtr.h" +#include "mozilla/Services.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/Telemetry.h" +#include "nsExceptionHandler.h" +#include "nsMemoryPressure.h" +#include "nsXULAppAPI.h" + +namespace mozilla { + +// Use this class as the initial value of +// nsAvailableMemoryWatcherBase::mCallback until RegisterCallback() is called +// so that nsAvailableMemoryWatcherBase does not have to check if its callback +// object is valid or not. +class NullTabUnloader final : public nsITabUnloader { + ~NullTabUnloader() = default; + + public: + NullTabUnloader() = default; + + NS_DECL_ISUPPORTS + NS_DECL_NSITABUNLOADER +}; + +NS_IMPL_ISUPPORTS(NullTabUnloader, nsITabUnloader) + +NS_IMETHODIMP NullTabUnloader::UnloadTabAsync() { + return NS_ERROR_NOT_IMPLEMENTED; +} + +StaticRefPtr<nsAvailableMemoryWatcherBase> + nsAvailableMemoryWatcherBase::sSingleton; + +/*static*/ +already_AddRefed<nsAvailableMemoryWatcherBase> +nsAvailableMemoryWatcherBase::GetSingleton() { + if (!sSingleton) { + sSingleton = CreateAvailableMemoryWatcher(); + ClearOnShutdown(&sSingleton); + } + + return do_AddRef(sSingleton); +} + +NS_IMPL_ISUPPORTS(nsAvailableMemoryWatcherBase, nsIAvailableMemoryWatcherBase); + +nsAvailableMemoryWatcherBase::nsAvailableMemoryWatcherBase() + : mMutex("nsAvailableMemoryWatcher mutex"), + mNumOfTabUnloading(0), + mNumOfMemoryPressure(0), + mTabUnloader(new NullTabUnloader), + mInteracting(false) { + MOZ_ASSERT(XRE_IsParentProcess(), + "Watching memory only in the main process."); +} + +const char* const nsAvailableMemoryWatcherBase::kObserverTopics[] = { + // Use this shutdown phase to make sure the instance is destroyed in GTest + "xpcom-shutdown", + "user-interaction-active", + "user-interaction-inactive", +}; + +nsresult nsAvailableMemoryWatcherBase::Init() { + MOZ_ASSERT(NS_IsMainThread(), + "nsAvailableMemoryWatcherBase needs to be initialized " + "in the main thread."); + + if (mObserverSvc) { + return NS_ERROR_ALREADY_INITIALIZED; + } + + mObserverSvc = services::GetObserverService(); + MOZ_ASSERT(mObserverSvc); + + for (auto topic : kObserverTopics) { + nsresult rv = mObserverSvc->AddObserver(this, topic, + /* ownsWeak */ false); + NS_ENSURE_SUCCESS(rv, rv); + } + return NS_OK; +} + +void nsAvailableMemoryWatcherBase::Shutdown() { + for (auto topic : kObserverTopics) { + mObserverSvc->RemoveObserver(this, topic); + } +} + +NS_IMETHODIMP +nsAvailableMemoryWatcherBase::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + MOZ_ASSERT(NS_IsMainThread()); + + if (strcmp(aTopic, "xpcom-shutdown") == 0) { + Shutdown(); + } else if (strcmp(aTopic, "user-interaction-inactive") == 0) { + mInteracting = false; +#ifdef MOZ_CRASHREPORTER + CrashReporter::SetInactiveStateStart(); +#endif + } else if (strcmp(aTopic, "user-interaction-active") == 0) { + mInteracting = true; +#ifdef MOZ_CRASHREPORTER + CrashReporter::ClearInactiveStateStart(); +#endif + } + return NS_OK; +} + +nsresult nsAvailableMemoryWatcherBase::RegisterTabUnloader( + nsITabUnloader* aTabUnloader) { + mTabUnloader = aTabUnloader; + return NS_OK; +} + +// This method is called as a part of UnloadTabAsync(). Like Notify(), if we +// call this method synchronously without releasing the lock first we can lead +// to deadlock. +nsresult nsAvailableMemoryWatcherBase::OnUnloadAttemptCompleted( + nsresult aResult) { + MutexAutoLock lock(mMutex); + switch (aResult) { + // A tab was unloaded successfully. + case NS_OK: + ++mNumOfTabUnloading; + break; + + // There was no unloadable tab. + case NS_ERROR_NOT_AVAILABLE: + ++mNumOfMemoryPressure; + NS_NotifyOfEventualMemoryPressure(MemoryPressureState::LowMemory); + break; + + // There was a pending task to unload a tab. + case NS_ERROR_ABORT: + break; + + default: + MOZ_ASSERT_UNREACHABLE("Unexpected aResult"); + break; + } + return NS_OK; +} + +void nsAvailableMemoryWatcherBase::UpdateLowMemoryTimeStamp() { + if (mLowMemoryStart.IsNull()) { + mLowMemoryStart = TimeStamp::NowLoRes(); + } +} + +void nsAvailableMemoryWatcherBase::RecordTelemetryEventOnHighMemory( + const MutexAutoLock&) { + Telemetry::SetEventRecordingEnabled("memory_watcher"_ns, true); + Telemetry::RecordEvent( + Telemetry::EventID::Memory_watcher_OnHighMemory_Stats, + Some(nsPrintfCString( + "%u,%u,%f", mNumOfTabUnloading, mNumOfMemoryPressure, + (TimeStamp::NowLoRes() - mLowMemoryStart).ToSeconds())), + Nothing()); + mNumOfTabUnloading = mNumOfMemoryPressure = 0; + mLowMemoryStart = TimeStamp(); +} + +// Define the fallback method for a platform for which a platform-specific +// CreateAvailableMemoryWatcher() is not defined. +#if defined(ANDROID) || \ + !defined(XP_WIN) && !defined(XP_DARWIN) && !defined(XP_LINUX) +already_AddRefed<nsAvailableMemoryWatcherBase> CreateAvailableMemoryWatcher() { + RefPtr instance(new nsAvailableMemoryWatcherBase); + return do_AddRef(instance); +} +#endif + +} // namespace mozilla diff --git a/xpcom/base/AvailableMemoryWatcher.h b/xpcom/base/AvailableMemoryWatcher.h new file mode 100644 index 0000000000..a5acfbb7c5 --- /dev/null +++ b/xpcom/base/AvailableMemoryWatcher.h @@ -0,0 +1,78 @@ +/* -*- 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_AvailableMemoryWatcher_h +#define mozilla_AvailableMemoryWatcher_h + +#include "mozilla/ipc/CrashReporterHost.h" +#include "mozilla/UniquePtr.h" +#include "MemoryPressureLevelMac.h" +#include "nsCOMPtr.h" +#include "nsIAvailableMemoryWatcherBase.h" +#include "nsIObserver.h" +#include "nsIObserverService.h" + +namespace mozilla { + +// This class implements a platform-independent part to watch the system's +// memory situation and invoke the registered callbacks when we detect +// a low-memory situation or a high-memory situation. +// The actual logic to monitor the memory status is implemented in a subclass +// of nsAvailableMemoryWatcherBase per platform. +class nsAvailableMemoryWatcherBase : public nsIAvailableMemoryWatcherBase, + public nsIObserver { + static StaticRefPtr<nsAvailableMemoryWatcherBase> sSingleton; + static const char* const kObserverTopics[]; + + TimeStamp mLowMemoryStart; + + protected: + // On Windows the publicly available methods (::Observe() and ::Notify()) are + // called on the main thread while the ::LowMemoryCallback() method is called + // by an external thread. All functions called from those must acquire a lock + // on this mutex before accessing the object's fields to prevent races. + // + // On Linux we might tell polling to start/stop from our polling thread + // or from the main thread during ::Observe(). + Mutex mMutex; + + uint32_t mNumOfTabUnloading MOZ_GUARDED_BY(mMutex); + uint32_t mNumOfMemoryPressure MOZ_GUARDED_BY(mMutex); + + nsCOMPtr<nsITabUnloader> mTabUnloader; + nsCOMPtr<nsIObserverService> mObserverSvc; + // Do not change this value off the main thread. + bool mInteracting; + + virtual ~nsAvailableMemoryWatcherBase() = default; + virtual nsresult Init(); + void Shutdown(); + void UpdateLowMemoryTimeStamp(); + void RecordTelemetryEventOnHighMemory(const MutexAutoLock&) + MOZ_REQUIRES(mMutex); + + public: + static already_AddRefed<nsAvailableMemoryWatcherBase> GetSingleton(); + + nsAvailableMemoryWatcherBase(); + +#if defined(XP_DARWIN) + virtual void OnMemoryPressureChanged(MacMemoryPressureLevel aNewLevel){}; + virtual void AddChildAnnotations( + const UniquePtr<ipc::CrashReporterHost>& aCrashReporter){}; +#endif + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIAVAILABLEMEMORYWATCHERBASE + NS_DECL_NSIOBSERVER +}; + +// Method to create a platform-specific object +already_AddRefed<nsAvailableMemoryWatcherBase> CreateAvailableMemoryWatcher(); + +} // namespace mozilla + +#endif // ifndef mozilla_AvailableMemoryWatcher_h diff --git a/xpcom/base/AvailableMemoryWatcherLinux.cpp b/xpcom/base/AvailableMemoryWatcherLinux.cpp new file mode 100644 index 0000000000..04927b7d46 --- /dev/null +++ b/xpcom/base/AvailableMemoryWatcherLinux.cpp @@ -0,0 +1,282 @@ +/* -*- 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 "AvailableMemoryWatcher.h" +#include "AvailableMemoryWatcherUtils.h" +#include "mozilla/Services.h" +#include "mozilla/StaticPrefs_browser.h" +#include "mozilla/Unused.h" +#include "nsAppRunner.h" +#include "nsIObserverService.h" +#include "nsISupports.h" +#include "nsITimer.h" +#include "nsIThread.h" +#include "nsMemoryPressure.h" + +namespace mozilla { + +// Linux has no native low memory detection. This class creates a timer that +// polls for low memory and sends a low memory notification if it notices a +// memory pressure event. +class nsAvailableMemoryWatcher final : public nsITimerCallback, + public nsINamed, + public nsAvailableMemoryWatcherBase { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSITIMERCALLBACK + NS_DECL_NSIOBSERVER + NS_DECL_NSINAMED + + nsresult Init() override; + nsAvailableMemoryWatcher(); + + void HandleLowMemory(); + void MaybeHandleHighMemory(); + + private: + ~nsAvailableMemoryWatcher() = default; + void StartPolling(const MutexAutoLock&); + void StopPolling(const MutexAutoLock&); + void ShutDown(); + void UpdateCrashAnnotation(const MutexAutoLock&); + static bool IsMemoryLow(); + + nsCOMPtr<nsITimer> mTimer MOZ_GUARDED_BY(mMutex); + nsCOMPtr<nsIThread> mThread MOZ_GUARDED_BY(mMutex); + + bool mPolling MOZ_GUARDED_BY(mMutex); + bool mUnderMemoryPressure MOZ_GUARDED_BY(mMutex); + + // Polling interval to check for low memory. In high memory scenarios, + // default to 5000 ms between each check. + static const uint32_t kHighMemoryPollingIntervalMS = 5000; + + // Polling interval to check for low memory. Default to 1000 ms between each + // check. Use this interval when memory is low, + static const uint32_t kLowMemoryPollingIntervalMS = 1000; +}; + +// A modern version of linux should keep memory information in the +// /proc/meminfo path. +static const char* kMeminfoPath = "/proc/meminfo"; + +nsAvailableMemoryWatcher::nsAvailableMemoryWatcher() + : mPolling(false), mUnderMemoryPressure(false) {} + +nsresult nsAvailableMemoryWatcher::Init() { + nsresult rv = nsAvailableMemoryWatcherBase::Init(); + if (NS_FAILED(rv)) { + return rv; + } + MutexAutoLock lock(mMutex); + mTimer = NS_NewTimer(); + nsCOMPtr<nsIThread> thread; + // We have to make our own thread here instead of using the background pool, + // because some low memory scenarios can cause the background pool to fill. + rv = NS_NewNamedThread("MemoryPoller", getter_AddRefs(thread)); + if (NS_FAILED(rv)) { + NS_WARNING("Couldn't make a thread for nsAvailableMemoryWatcher."); + // In this scenario we can't poll for low memory, since we can't dispatch + // to our memory watcher thread. + return rv; + } + mThread = thread; + + // Set the crash annotation to its initial state. + UpdateCrashAnnotation(lock); + + StartPolling(lock); + + return NS_OK; +} + +already_AddRefed<nsAvailableMemoryWatcherBase> CreateAvailableMemoryWatcher() { + RefPtr watcher(new nsAvailableMemoryWatcher); + + if (NS_FAILED(watcher->Init())) { + return do_AddRef(new nsAvailableMemoryWatcherBase); + } + + return watcher.forget(); +} + +NS_IMPL_ISUPPORTS_INHERITED(nsAvailableMemoryWatcher, + nsAvailableMemoryWatcherBase, nsITimerCallback, + nsIObserver, nsINamed); + +void nsAvailableMemoryWatcher::StopPolling(const MutexAutoLock&) + MOZ_REQUIRES(mMutex) { + if (mPolling && mTimer) { + // stop dispatching memory checks to the thread. + mTimer->Cancel(); + mPolling = false; + } +} + +// Check /proc/meminfo for low memory. Largely C method for reading +// /proc/meminfo. +/* static */ +bool nsAvailableMemoryWatcher::IsMemoryLow() { + MemoryInfo memInfo{0, 0}; + nsresult rv = ReadMemoryFile(kMeminfoPath, memInfo); + + if (NS_FAILED(rv) || (memInfo.memAvailable == 0) || (memInfo.memTotal == 0)) { + // If memAvailable cannot be found, then we are using an older system. + // We can't accurately poll on this. + // If memTotal is zero we can't calculate how much memory we're using. + return false; + } + + unsigned long memoryAsPercentage = + (memInfo.memAvailable * 100) / memInfo.memTotal; + + return memoryAsPercentage <= + StaticPrefs::browser_low_commit_space_threshold_percent() || + memInfo.memAvailable < + StaticPrefs::browser_low_commit_space_threshold_mb() * 1024; +} + +void nsAvailableMemoryWatcher::ShutDown() { + nsCOMPtr<nsIThread> thread; + { + MutexAutoLock lock(mMutex); + if (mTimer) { + mTimer->Cancel(); + mTimer = nullptr; + } + thread = mThread.forget(); + } + // thread->Shutdown() spins a nested event loop while waiting for the thread + // to end. But the thread might execute some previously dispatched event that + // wants to lock our mutex, too, before arriving at the shutdown event. + if (thread) { + thread->Shutdown(); + } +} + +// We will use this to poll for low memory. +NS_IMETHODIMP +nsAvailableMemoryWatcher::Notify(nsITimer* aTimer) { + MutexAutoLock lock(mMutex); + if (!mThread) { + // If we've made it this far and there's no |mThread|, + // we might have failed to dispatch it for some reason. + MOZ_ASSERT(mThread); + return NS_ERROR_FAILURE; + } + nsresult rv = mThread->Dispatch( + NS_NewRunnableFunction("MemoryPoller", [self = RefPtr{this}]() { + if (self->IsMemoryLow()) { + self->HandleLowMemory(); + } else { + self->MaybeHandleHighMemory(); + } + })); + + if NS_FAILED (rv) { + NS_WARNING("Cannot dispatch memory polling event."); + } + return NS_OK; +} + +void nsAvailableMemoryWatcher::HandleLowMemory() { + MutexAutoLock lock(mMutex); + if (!mTimer) { + // We have been shut down from outside while in flight. + return; + } + if (!mUnderMemoryPressure) { + mUnderMemoryPressure = true; + UpdateCrashAnnotation(lock); + // Poll more frequently under memory pressure. + StartPolling(lock); + } + UpdateLowMemoryTimeStamp(); + // We handle low memory offthread, but we want to unload + // tabs only from the main thread, so we will dispatch this + // back to the main thread. + // Since we are doing this async, we don't need to unlock the mutex first; + // the AutoLock will unlock the mutex when we finish the dispatch. + NS_DispatchToMainThread(NS_NewRunnableFunction( + "nsAvailableMemoryWatcher::OnLowMemory", + [self = RefPtr{this}]() { self->mTabUnloader->UnloadTabAsync(); })); +} + +void nsAvailableMemoryWatcher::UpdateCrashAnnotation(const MutexAutoLock&) + MOZ_REQUIRES(mMutex) { + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::LinuxUnderMemoryPressure, + mUnderMemoryPressure); +} + +// If memory is not low, we may need to dispatch an +// event for it if we have been under memory pressure. +// We can also adjust our polling interval. +void nsAvailableMemoryWatcher::MaybeHandleHighMemory() { + MutexAutoLock lock(mMutex); + if (!mTimer) { + // We have been shut down from outside while in flight. + return; + } + if (mUnderMemoryPressure) { + RecordTelemetryEventOnHighMemory(lock); + NS_NotifyOfEventualMemoryPressure(MemoryPressureState::NoPressure); + mUnderMemoryPressure = false; + UpdateCrashAnnotation(lock); + } + StartPolling(lock); +} + +// When we change the polling interval, we will need to restart the timer +// on the new interval. +void nsAvailableMemoryWatcher::StartPolling(const MutexAutoLock& aLock) + MOZ_REQUIRES(mMutex) { + uint32_t pollingInterval = mUnderMemoryPressure + ? kLowMemoryPollingIntervalMS + : kHighMemoryPollingIntervalMS; + if (!mPolling) { + // Restart the timer with the new interval if it has stopped. + // For testing, use a small polling interval. + if (NS_SUCCEEDED( + mTimer->InitWithCallback(this, gIsGtest ? 10 : pollingInterval, + nsITimer::TYPE_REPEATING_SLACK))) { + mPolling = true; + } + } else { + mTimer->SetDelay(gIsGtest ? 10 : pollingInterval); + } +} + +// Observe events for shutting down and starting/stopping the timer. +NS_IMETHODIMP +nsAvailableMemoryWatcher::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + nsresult rv = nsAvailableMemoryWatcherBase::Observe(aSubject, aTopic, aData); + if (NS_FAILED(rv)) { + return rv; + } + + if (strcmp(aTopic, "xpcom-shutdown") == 0) { + ShutDown(); + } else { + MutexAutoLock lock(mMutex); + if (mTimer) { + if (strcmp(aTopic, "user-interaction-active") == 0) { + StartPolling(lock); + } else if (strcmp(aTopic, "user-interaction-inactive") == 0) { + StopPolling(lock); + } + } + } + + return NS_OK; +} + +NS_IMETHODIMP nsAvailableMemoryWatcher::GetName(nsACString& aName) { + aName.AssignLiteral("nsAvailableMemoryWatcher"); + return NS_OK; +} + +} // namespace mozilla diff --git a/xpcom/base/AvailableMemoryWatcherMac.cpp b/xpcom/base/AvailableMemoryWatcherMac.cpp new file mode 100644 index 0000000000..d6f2d16b30 --- /dev/null +++ b/xpcom/base/AvailableMemoryWatcherMac.cpp @@ -0,0 +1,634 @@ +/* -*- 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 <sys/sysctl.h> +#include <sys/types.h> +#include <time.h> + +#include "AvailableMemoryWatcher.h" +#include "Logging.h" +#include "mozilla/Preferences.h" +#include "nsICrashReporter.h" +#include "nsISupports.h" +#include "nsITimer.h" +#include "nsMemoryPressure.h" +#include "nsPrintfCString.h" + +#define MP_LOG(...) MOZ_LOG(gMPLog, mozilla::LogLevel::Debug, (__VA_ARGS__)) +static mozilla::LazyLogModule gMPLog("MemoryPressure"); + +namespace mozilla { + +/* + * The Mac AvailableMemoryWatcher works as follows. When the OS memory pressure + * level changes on macOS, nsAvailableMemoryWatcher::OnMemoryPressureChanged() + * is called with the new memory pressure level. The level is represented in + * Gecko by a MacMemoryPressureLevel instance and represents the states of + * normal, warning, or critical which correspond to the native levels. When the + * browser launches, the initial level is determined using a sysctl. Which + * actions are taken in the browser in response to memory pressure, and the + * level (warning or critical) which trigger the reponse is configurable with + * prefs to make it easier to perform experiments to study how the response + * affects the user experience. + * + * By default, the browser responds by attempting to reduce memory use when the + * OS transitions to the critical level and while it stays in the critical + * level. i.e., "critical" OS memory pressure is the default threshold for the + * low memory response. Setting pref "browser.lowMemoryResponseOnWarn" to true + * changes the memory response to occur at the "warning" level which is less + * severe than "critical". When entering the critical level, we begin polling + * the memory pressure level every 'n' milliseconds (specified via the pref + * "browser.lowMemoryPollingIntervalMS"). Each time the poller wakes up and + * finds the OS still under memory pressure, the low memory response is + * executed. + * + * By default, the memory pressure response is, in order, to + * 1) call nsITabUnloader::UnloadTabAsync(), + * 2) if no tabs could be unloaded, issue a Gecko + * MemoryPressureState::LowMemory notification. + * The response can be changed via the pref "browser.lowMemoryResponseMask" to + * limit the actions to only tab unloading or Gecko memory pressure + * notifications. + * + * Polling occurs on the main thread because, at each polling interval, we + * call into the tab unloader which requires being on the main thread. + * Polling only occurs while under OS memory pressure at the critical (by + * default) level. + */ +class nsAvailableMemoryWatcher final : public nsITimerCallback, + public nsINamed, + public nsAvailableMemoryWatcherBase { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIOBSERVER + NS_DECL_NSITIMERCALLBACK + NS_DECL_NSINAMED + + nsAvailableMemoryWatcher(); + nsresult Init() override; + + void OnMemoryPressureChanged(MacMemoryPressureLevel aLevel) override; + void AddChildAnnotations( + const UniquePtr<ipc::CrashReporterHost>& aCrashReporter) override; + + private: + ~nsAvailableMemoryWatcher(){}; + + void OnMemoryPressureChangedInternal(MacMemoryPressureLevel aNewLevel, + bool aIsInitialLevel); + + // Override OnUnloadAttemptCompleted() so that we can control whether + // or not a Gecko memory-pressure event is sent after a tab unload attempt. + // This method is called externally by the tab unloader after a tab unload + // attempt. It is used internally when tab unloading is disabled in + // mResponseMask. + nsresult OnUnloadAttemptCompleted(nsresult aResult) override; + + void OnShutdown(); + void OnPrefChange(); + + void InitParentAnnotations(); + void UpdateParentAnnotations(); + + void AddParentAnnotation(CrashReporter::Annotation aAnnotation, + nsAutoCString aString) { + CrashReporter::AnnotateCrashReport(aAnnotation, aString); + } + void AddParentAnnotation(CrashReporter::Annotation aAnnotation, + uint32_t aData) { + CrashReporter::AnnotateCrashReport(aAnnotation, aData); + } + + void LowMemoryResponse(); + void StartPolling(); + void StopPolling(); + void RestartPolling(); + inline bool IsPolling() { return mTimer; } + + void ReadSysctls(); + + // This enum represents the allowed values for the pref that controls + // the low memory response - "browser.lowMemoryResponseMask". Specifically, + // whether or not we unload tabs and/or issue the Gecko "memory-pressure" + // internal notification. For tab unloading, the pref + // "browser.tabs.unloadOnLowMemory" must also be set. + enum ResponseMask { + eNone = 0x0, + eTabUnload = 0x1, + eInternalMemoryPressure = 0x2, + eAll = 0x3, + }; + static constexpr char kResponseMask[] = "browser.lowMemoryResponseMask"; + static const uint32_t kResponseMaskDefault; + static const uint32_t kResponseMaskMax; + + // Pref for controlling how often we wake up during an OS memory pressure + // time period. At each wakeup, we unload tabs and issue the Gecko + // "memory-pressure" internal notification. When not under OS memory pressure, + // polling is disabled. + static constexpr char kPollingIntervalMS[] = + "browser.lowMemoryPollingIntervalMS"; + static const uint32_t kPollingIntervalMaxMS; + static const uint32_t kPollingIntervalMinMS; + static const uint32_t kPollingIntervalDefaultMS; + + static constexpr char kResponseOnWarn[] = "browser.lowMemoryResponseOnWarn"; + static const bool kResponseLevelOnWarnDefault = false; + + // Init has been called. + bool mInitialized; + + // The memory pressure reported to the application by macOS. + MacMemoryPressureLevel mLevel; + + // The OS memory pressure level that triggers the response. + MacMemoryPressureLevel mResponseLevel; + + // The value of the kern.memorystatus_vm_pressure_level sysctl. The OS + // notifies the application when the memory pressure level changes, + // but the sysctl value can be read at any time. Unofficially, the sysctl + // value corresponds to the OS memory pressure level with 4=>critical, + // 2=>warning, and 1=>normal (values from kernel event.h file). + uint32_t mLevelSysctl; + static const int kSysctlLevelNormal = 0x1; + static const int kSysctlLevelWarning = 0x2; + static const int kSysctlLevelCritical = 0x4; + + // The value of the kern.memorystatus_level sysctl. Unofficially, + // this is the percentage of available memory. (Also readable + // via the undocumented memorystatus_get_level syscall.) + int mAvailMemSysctl; + + // The string representation of `mLevel`. i.e., normal, warning, or critical. + // Set to "unset" until a memory pressure change is reported to the process + // by the OS. + nsAutoCString mLevelStr; + + // Timestamps for memory pressure level changes. Specifically, the Unix + // time in string form. Saved as Unix time to allow comparisons with + // the crash time. + nsAutoCString mNormalTimeStr; + nsAutoCString mWarningTimeStr; + nsAutoCString mCriticalTimeStr; + + nsCOMPtr<nsITimer> mTimer; // non-null indicates the timer is active + + // Saved pref values. + uint32_t mPollingInterval; + uint32_t mResponseMask; +}; + +const uint32_t nsAvailableMemoryWatcher::kResponseMaskDefault = + ResponseMask::eAll; +const uint32_t nsAvailableMemoryWatcher::kResponseMaskMax = ResponseMask::eAll; + +// 10 seconds +const uint32_t nsAvailableMemoryWatcher::kPollingIntervalDefaultMS = 10'000; +// 10 minutes +const uint32_t nsAvailableMemoryWatcher::kPollingIntervalMaxMS = 600'000; +// 100 milliseconds +const uint32_t nsAvailableMemoryWatcher::kPollingIntervalMinMS = 100; + +NS_IMPL_ISUPPORTS_INHERITED(nsAvailableMemoryWatcher, + nsAvailableMemoryWatcherBase, nsIObserver, + nsITimerCallback, nsINamed) + +nsAvailableMemoryWatcher::nsAvailableMemoryWatcher() + : mInitialized(false), + mLevel(MacMemoryPressureLevel::Value::eUnset), + mResponseLevel(MacMemoryPressureLevel::Value::eCritical), + mLevelSysctl(0xFFFFFFFF), + mAvailMemSysctl(-1), + mLevelStr("Unset"), + mNormalTimeStr("Unset"), + mWarningTimeStr("Unset"), + mCriticalTimeStr("Unset"), + mPollingInterval(0), + mResponseMask(ResponseMask::eAll) {} + +nsresult nsAvailableMemoryWatcher::Init() { + nsresult rv = nsAvailableMemoryWatcherBase::Init(); + if (NS_FAILED(rv)) { + return rv; + } + + // Users of nsAvailableMemoryWatcher should use + // nsAvailableMemoryWatcherBase::GetSingleton() and not call Init directly. + MOZ_ASSERT(!mInitialized); + if (mInitialized) { + return NS_ERROR_ALREADY_INITIALIZED; + } + + // Read polling frequency pref + mPollingInterval = + Preferences::GetUint(kPollingIntervalMS, kPollingIntervalDefaultMS); + mPollingInterval = std::clamp(mPollingInterval, kPollingIntervalMinMS, + kPollingIntervalMaxMS); + + // Read response bitmask pref which (along with the main tab unloading + // preference) controls whether or not tab unloading and Gecko (internal) + // memory pressure notifications will be sent. The main tab unloading + // preference must also be enabled for tab unloading to occur. + mResponseMask = Preferences::GetUint(kResponseMask, kResponseMaskDefault); + if (mResponseMask > kResponseMaskMax) { + mResponseMask = kResponseMaskMax; + } + + // Read response level pref + if (Preferences::GetBool(kResponseOnWarn, kResponseLevelOnWarnDefault)) { + mResponseLevel = MacMemoryPressureLevel::Value::eWarning; + } else { + mResponseLevel = MacMemoryPressureLevel::Value::eCritical; + } + + ReadSysctls(); + MP_LOG("Initial memory pressure sysctl: %d", mLevelSysctl); + MP_LOG("Initial available memory sysctl: %d", mAvailMemSysctl); + + // Set the initial state of all annotations for parent crash reports. + // Content process crash reports are set when a crash occurs and + // AddChildAnnotations() is called. + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::MacMemoryPressure, mLevelStr); + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::MacMemoryPressureNormalTime, mNormalTimeStr); + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::MacMemoryPressureWarningTime, mWarningTimeStr); + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::MacMemoryPressureCriticalTime, + mCriticalTimeStr); + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::MacMemoryPressureSysctl, mLevelSysctl); + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::MacAvailableMemorySysctl, mAvailMemSysctl); + + // To support running experiments, handle pref + // changes without requiring a browser restart. + rv = Preferences::AddStrongObserver(this, kResponseMask); + if (NS_FAILED(rv)) { + NS_WARNING( + nsPrintfCString("Failed to add %s observer", kResponseMask).get()); + } + rv = Preferences::AddStrongObserver(this, kPollingIntervalMS); + if (NS_FAILED(rv)) { + NS_WARNING( + nsPrintfCString("Failed to add %s observer", kPollingIntervalMS).get()); + } + rv = Preferences::AddStrongObserver(this, kResponseOnWarn); + if (NS_FAILED(rv)) { + NS_WARNING( + nsPrintfCString("Failed to add %s observer", kResponseOnWarn).get()); + } + + // Use the memory pressure sysctl to initialize our memory pressure state. + MacMemoryPressureLevel initialLevel; + switch (mLevelSysctl) { + case kSysctlLevelNormal: + initialLevel = MacMemoryPressureLevel::Value::eNormal; + break; + case kSysctlLevelWarning: + initialLevel = MacMemoryPressureLevel::Value::eWarning; + break; + case kSysctlLevelCritical: + initialLevel = MacMemoryPressureLevel::Value::eCritical; + break; + default: + initialLevel = MacMemoryPressureLevel::Value::eUnexpected; + } + + OnMemoryPressureChangedInternal(initialLevel, /* aIsInitialLevel */ true); + mInitialized = true; + return NS_OK; +} + +already_AddRefed<nsAvailableMemoryWatcherBase> CreateAvailableMemoryWatcher() { + // Users of nsAvailableMemoryWatcher should use + // nsAvailableMemoryWatcherBase::GetSingleton(). + RefPtr watcher(new nsAvailableMemoryWatcher()); + watcher->Init(); + return watcher.forget(); +} + +// Update the memory pressure level, level change timestamps, and sysctl +// level crash report annotations. +void nsAvailableMemoryWatcher::UpdateParentAnnotations() { + // Generate a string representation of the current Unix time. + time_t timeChanged = time(NULL); + nsAutoCString timeChangedString; + timeChangedString = + nsPrintfCString("%" PRIu64, static_cast<uint64_t>(timeChanged)); + + nsAutoCString pressureLevelString; + Maybe<CrashReporter::Annotation> pressureLevelKey; + + switch (mLevel.GetValue()) { + case MacMemoryPressureLevel::Value::eNormal: + mNormalTimeStr = timeChangedString; + pressureLevelString = "Normal"; + pressureLevelKey.emplace( + CrashReporter::Annotation::MacMemoryPressureNormalTime); + break; + case MacMemoryPressureLevel::Value::eWarning: + mWarningTimeStr = timeChangedString; + pressureLevelString = "Warning"; + pressureLevelKey.emplace( + CrashReporter::Annotation::MacMemoryPressureWarningTime); + break; + case MacMemoryPressureLevel::Value::eCritical: + mCriticalTimeStr = timeChangedString; + pressureLevelString = "Critical"; + pressureLevelKey.emplace( + CrashReporter::Annotation::MacMemoryPressureCriticalTime); + break; + default: + pressureLevelString = "Unexpected"; + break; + } + + // Save the current memory pressure level. + AddParentAnnotation(CrashReporter::Annotation::MacMemoryPressure, + pressureLevelString); + + // Save the time we transitioned to the current memory pressure level. + if (pressureLevelKey.isSome()) { + AddParentAnnotation(pressureLevelKey.value(), timeChangedString); + } + + AddParentAnnotation(CrashReporter::Annotation::MacMemoryPressureSysctl, + mLevelSysctl); + AddParentAnnotation(CrashReporter::Annotation::MacAvailableMemorySysctl, + mAvailMemSysctl); +} + +void nsAvailableMemoryWatcher::ReadSysctls() { + // Pressure level + uint32_t level; + size_t size = sizeof(level); + if (sysctlbyname("kern.memorystatus_vm_pressure_level", &level, &size, NULL, + 0) == -1) { + MP_LOG("Failure reading memory pressure sysctl"); + } + mLevelSysctl = level; + + // Available memory percent + int availPercent; + size = sizeof(availPercent); + if (sysctlbyname("kern.memorystatus_level", &availPercent, &size, NULL, 0) == + -1) { + MP_LOG("Failure reading available memory level"); + } + mAvailMemSysctl = availPercent; +} + +/* virtual */ +void nsAvailableMemoryWatcher::OnMemoryPressureChanged( + MacMemoryPressureLevel aNewLevel) { + MOZ_ASSERT(mInitialized); + OnMemoryPressureChangedInternal(aNewLevel, /* aIsInitialLevel */ false); +} + +void nsAvailableMemoryWatcher::OnMemoryPressureChangedInternal( + MacMemoryPressureLevel aNewLevel, bool aIsInitialLevel) { + MOZ_ASSERT(mInitialized || aIsInitialLevel); + MP_LOG("MemoryPressureChange: existing level: %s, new level: %s", + mLevel.ToString(), aNewLevel.ToString()); + + // If 'aNewLevel' is not one of normal, warning, or critical, ASSERT + // here so we can debug this scenario. For non-debug builds, ignore + // the unexpected value which will be logged in crash reports. + MOZ_ASSERT(aNewLevel.IsNormal() || aNewLevel.IsWarningOrAbove()); + + if (mLevel == aNewLevel) { + return; + } + + // Start the memory pressure response if the new level is high enough + // and the existing level was not. + if ((mLevel < mResponseLevel) && (aNewLevel >= mResponseLevel)) { + UpdateLowMemoryTimeStamp(); + LowMemoryResponse(); + if (mResponseMask) { + StartPolling(); + } + } + + // End the memory pressure reponse if the new level is not high enough. + if ((mLevel >= mResponseLevel) && (aNewLevel < mResponseLevel)) { + { + MutexAutoLock lock(mMutex); + RecordTelemetryEventOnHighMemory(lock); + } + StopPolling(); + MP_LOG("Issuing MemoryPressureState::NoPressure"); + NS_NotifyOfMemoryPressure(MemoryPressureState::NoPressure); + } + + mLevel = aNewLevel; + + if (!aIsInitialLevel) { + // Sysctls are already read by ::Init(). + ReadSysctls(); + MP_LOG("level sysctl: %d, available memory: %d percent", mLevelSysctl, + mAvailMemSysctl); + } + UpdateParentAnnotations(); +} + +/* virtual */ +// Add all annotations to the provided crash reporter instance. +void nsAvailableMemoryWatcher::AddChildAnnotations( + const UniquePtr<ipc::CrashReporterHost>& aCrashReporter) { + aCrashReporter->AddAnnotation(CrashReporter::Annotation::MacMemoryPressure, + mLevelStr); + aCrashReporter->AddAnnotation( + CrashReporter::Annotation::MacMemoryPressureNormalTime, mNormalTimeStr); + aCrashReporter->AddAnnotation( + CrashReporter::Annotation::MacMemoryPressureWarningTime, mWarningTimeStr); + aCrashReporter->AddAnnotation( + CrashReporter::Annotation::MacMemoryPressureCriticalTime, + mCriticalTimeStr); + aCrashReporter->AddAnnotation( + CrashReporter::Annotation::MacMemoryPressureSysctl, mLevelSysctl); + aCrashReporter->AddAnnotation( + CrashReporter::Annotation::MacAvailableMemorySysctl, mAvailMemSysctl); +} + +void nsAvailableMemoryWatcher::LowMemoryResponse() { + if (mResponseMask & ResponseMask::eTabUnload) { + MP_LOG("Attempting tab unload"); + mTabUnloader->UnloadTabAsync(); + } else { + // Re-use OnUnloadAttemptCompleted() to issue the internal + // memory pressure event. + OnUnloadAttemptCompleted(NS_ERROR_NOT_AVAILABLE); + } +} + +NS_IMETHODIMP +nsAvailableMemoryWatcher::Notify(nsITimer* aTimer) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mLevel >= mResponseLevel); + LowMemoryResponse(); + return NS_OK; +} + +// Override OnUnloadAttemptCompleted() so that we can issue Gecko memory +// pressure notifications only if eInternalMemoryPressure is set in +// mResponseMask. When called from the tab unloader, an |aResult| value of +// NS_OK indicates the tab unloader successfully unloaded a tab. +// NS_ERROR_NOT_AVAILABLE indicates the tab unloader did not unload any tabs. +NS_IMETHODIMP +nsAvailableMemoryWatcher::OnUnloadAttemptCompleted(nsresult aResult) { + // On MacOS we don't access these members offthread; however we do on other + // OSes and so they are guarded by the mutex. + MutexAutoLock lock(mMutex); + switch (aResult) { + // A tab was unloaded successfully. + case NS_OK: + MP_LOG("Tab unloaded"); + ++mNumOfTabUnloading; + break; + + // Either the tab unloader found no unloadable tabs OR we've been called + // locally to explicitly issue the internal memory pressure event because + // tab unloading is disabled in |mResponseMask|. In either case, attempt + // to reduce memory use using the internal memory pressure notification. + case NS_ERROR_NOT_AVAILABLE: + if (mResponseMask & ResponseMask::eInternalMemoryPressure) { + ++mNumOfMemoryPressure; + MP_LOG("Tab not unloaded"); + MP_LOG("Issuing MemoryPressureState::LowMemory"); + NS_NotifyOfEventualMemoryPressure(MemoryPressureState::LowMemory); + } + break; + + // There was a pending task to unload a tab. + case NS_ERROR_ABORT: + break; + + default: + MOZ_ASSERT_UNREACHABLE("Unexpected aResult"); + break; + } + return NS_OK; +} + +NS_IMETHODIMP +nsAvailableMemoryWatcher::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + nsresult rv = nsAvailableMemoryWatcherBase::Observe(aSubject, aTopic, aData); + if (NS_FAILED(rv)) { + return rv; + } + + if (strcmp(aTopic, "xpcom-shutdown") == 0) { + OnShutdown(); + } else if (strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) == 0) { + OnPrefChange(); + } + return NS_OK; +} + +void nsAvailableMemoryWatcher::OnShutdown() { + StopPolling(); + Preferences::RemoveObserver(this, kResponseMask); + Preferences::RemoveObserver(this, kPollingIntervalMS); +} + +void nsAvailableMemoryWatcher::OnPrefChange() { + MP_LOG("OnPrefChange()"); + // Handle the polling interval changing. + uint32_t pollingInterval = Preferences::GetUint(kPollingIntervalMS); + if (pollingInterval != mPollingInterval) { + mPollingInterval = std::clamp(pollingInterval, kPollingIntervalMinMS, + kPollingIntervalMaxMS); + RestartPolling(); + } + + // Handle the response mask changing. + uint32_t responseMask = Preferences::GetUint(kResponseMask); + if (mResponseMask != responseMask) { + mResponseMask = std::min(responseMask, kResponseMaskMax); + + // Do we need to turn on polling? + if (mResponseMask && (mLevel >= mResponseLevel) && !IsPolling()) { + StartPolling(); + } + + // Do we need to turn off polling? + if (!mResponseMask && IsPolling()) { + StopPolling(); + } + } + + // Handle the response level changing. + MacMemoryPressureLevel newResponseLevel; + if (Preferences::GetBool(kResponseOnWarn, kResponseLevelOnWarnDefault)) { + newResponseLevel = MacMemoryPressureLevel::Value::eWarning; + } else { + newResponseLevel = MacMemoryPressureLevel::Value::eCritical; + } + if (newResponseLevel == mResponseLevel) { + return; + } + + // Do we need to turn on polling? + if (mResponseMask && (newResponseLevel <= mLevel)) { + UpdateLowMemoryTimeStamp(); + LowMemoryResponse(); + StartPolling(); + } + + // Do we need to turn off polling? + if (IsPolling() && (newResponseLevel > mLevel)) { + { + MutexAutoLock lock(mMutex); + RecordTelemetryEventOnHighMemory(lock); + } + StopPolling(); + MP_LOG("Issuing MemoryPressureState::NoPressure"); + NS_NotifyOfMemoryPressure(MemoryPressureState::NoPressure); + } + mResponseLevel = newResponseLevel; +} + +void nsAvailableMemoryWatcher::StartPolling() { + MOZ_ASSERT(NS_IsMainThread()); + if (!mTimer) { + MP_LOG("Starting poller"); + mTimer = NS_NewTimer(); + if (mTimer) { + mTimer->InitWithCallback(this, mPollingInterval, + nsITimer::TYPE_REPEATING_SLACK); + } + } +} + +void nsAvailableMemoryWatcher::StopPolling() { + MOZ_ASSERT(NS_IsMainThread()); + if (mTimer) { + MP_LOG("Pausing poller"); + mTimer->Cancel(); + mTimer = nullptr; + } +} + +void nsAvailableMemoryWatcher::RestartPolling() { + if (IsPolling()) { + StopPolling(); + StartPolling(); + } else { + MOZ_ASSERT(!mTimer); + } +} + +NS_IMETHODIMP +nsAvailableMemoryWatcher::GetName(nsACString& aName) { + aName.AssignLiteral("nsAvailableMemoryWatcher"); + return NS_OK; +} + +} // namespace mozilla diff --git a/xpcom/base/AvailableMemoryWatcherUtils.h b/xpcom/base/AvailableMemoryWatcherUtils.h new file mode 100644 index 0000000000..2d70fe5fd0 --- /dev/null +++ b/xpcom/base/AvailableMemoryWatcherUtils.h @@ -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/. */ + +#ifndef mozilla_AvailableMemoryWatcherUtils_h +#define mozilla_AvailableMemoryWatcherUtils_h + +#include "mozilla/Attributes.h" +#include "nsISupportsUtils.h" // For nsresult + +namespace mozilla { + +struct MemoryInfo { + unsigned long memTotal; + unsigned long memAvailable; +}; +// Check /proc/meminfo for low memory. Largely C method for reading +// /proc/meminfo. +MOZ_MAYBE_UNUSED +static nsresult ReadMemoryFile(const char* meminfoPath, MemoryInfo& aResult) { + FILE* fd; + if ((fd = fopen(meminfoPath, "r")) == nullptr) { + // Meminfo somehow unreachable + return NS_ERROR_FAILURE; + } + + char buff[128]; + + /* The first few lines of meminfo look something like this: + * MemTotal: 65663448 kB + * MemFree: 57368112 kB + * MemAvailable: 61852700 kB + * We mostly care about the available versus the total. We calculate our + * memory thresholds using this, and when memory drops below 5% we consider + * this to be a memory pressure event. In practice these lines aren't + * necessarily in order, but we can simply search for MemTotal + * and MemAvailable. + */ + char namebuffer[20]; + while ((fgets(buff, sizeof(buff), fd)) != nullptr) { + if (strstr(buff, "MemTotal:")) { + sscanf(buff, "%s %lu ", namebuffer, &aResult.memTotal); + } + if (strstr(buff, "MemAvailable:")) { + sscanf(buff, "%s %lu ", namebuffer, &aResult.memAvailable); + } + } + fclose(fd); + return NS_OK; +} + +} // namespace mozilla + +#endif // ifndef mozilla_AvailableMemoryWatcherUtils_h diff --git a/xpcom/base/AvailableMemoryWatcherWin.cpp b/xpcom/base/AvailableMemoryWatcherWin.cpp new file mode 100644 index 0000000000..cd027366de --- /dev/null +++ b/xpcom/base/AvailableMemoryWatcherWin.cpp @@ -0,0 +1,401 @@ +/* -*- 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 "AvailableMemoryWatcher.h" +#include "mozilla/Atomics.h" +#include "mozilla/Services.h" +#include "mozilla/StaticPrefs_browser.h" +#include "nsAppRunner.h" +#include "nsExceptionHandler.h" +#include "nsICrashReporter.h" +#include "nsIObserver.h" +#include "nsISupports.h" +#include "nsITimer.h" +#include "nsMemoryPressure.h" +#include "nsServiceManagerUtils.h" +#include "nsWindowsHelpers.h" + +#include <memoryapi.h> + +extern mozilla::Atomic<uint32_t, mozilla::MemoryOrdering::Relaxed> + sNumLowPhysicalMemEvents; + +namespace mozilla { + +// This class is used to monitor low memory events delivered by Windows via +// memory resource notification objects. When we enter a low memory scenario +// the LowMemoryCallback() is invoked by Windows. This initial call triggers +// an nsITimer that polls to see when the low memory condition has been lifted. +// When it has, we'll stop polling and start waiting for the next +// LowMemoryCallback(). Meanwhile, the polling may be stopped and restarted by +// user-interaction events from the observer service. +class nsAvailableMemoryWatcher final : public nsITimerCallback, + public nsINamed, + public nsAvailableMemoryWatcherBase { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIOBSERVER + NS_DECL_NSITIMERCALLBACK + NS_DECL_NSINAMED + + nsAvailableMemoryWatcher(); + nsresult Init() override; + + private: + static VOID CALLBACK LowMemoryCallback(PVOID aContext, BOOLEAN aIsTimer); + static void RecordLowMemoryEvent(); + static bool IsCommitSpaceLow(); + + ~nsAvailableMemoryWatcher(); + bool RegisterMemoryResourceHandler(const MutexAutoLock& aLock) + MOZ_REQUIRES(mMutex); + void UnregisterMemoryResourceHandler(const MutexAutoLock&) + MOZ_REQUIRES(mMutex); + void MaybeSaveMemoryReport(const MutexAutoLock&) MOZ_REQUIRES(mMutex); + void Shutdown(const MutexAutoLock& aLock) MOZ_REQUIRES(mMutex); + bool ListenForLowMemory(const MutexAutoLock&) MOZ_REQUIRES(mMutex); + void OnLowMemory(const MutexAutoLock&) MOZ_REQUIRES(mMutex); + void OnHighMemory(const MutexAutoLock&) MOZ_REQUIRES(mMutex); + void StartPollingIfUserInteracting(const MutexAutoLock& aLock) + MOZ_REQUIRES(mMutex); + void StopPolling(const MutexAutoLock&) MOZ_REQUIRES(mMutex); + void StopPollingIfUserIdle(const MutexAutoLock&) MOZ_REQUIRES(mMutex); + void OnUserInteracting(const MutexAutoLock&) MOZ_REQUIRES(mMutex); + + nsCOMPtr<nsITimer> mTimer MOZ_GUARDED_BY(mMutex); + nsAutoHandle mLowMemoryHandle MOZ_GUARDED_BY(mMutex); + HANDLE mWaitHandle MOZ_GUARDED_BY(mMutex); + bool mPolling MOZ_GUARDED_BY(mMutex); + + // Indicates whether to start a timer when user interaction is notified. + // This flag is needed because the low-memory callback may be triggered when + // the user is inactive and we want to delay-start the timer. + bool mNeedToRestartTimerOnUserInteracting MOZ_GUARDED_BY(mMutex); + // Indicate that the available commit space is low. The timer handler needs + // this flag because it is triggered by the low physical memory regardless + // of the available commit space. + bool mUnderMemoryPressure MOZ_GUARDED_BY(mMutex); + + bool mSavedReport MOZ_GUARDED_BY(mMutex); + bool mIsShutdown MOZ_GUARDED_BY(mMutex); + + // Members below this line are used only in the main thread. + // No lock is needed. + + // Don't fire a low-memory notification more often than this interval. + uint32_t mPollingInterval; +}; + +NS_IMPL_ISUPPORTS_INHERITED(nsAvailableMemoryWatcher, + nsAvailableMemoryWatcherBase, nsIObserver, + nsITimerCallback, nsINamed) + +nsAvailableMemoryWatcher::nsAvailableMemoryWatcher() + : mWaitHandle(nullptr), + mPolling(false), + mNeedToRestartTimerOnUserInteracting(false), + mUnderMemoryPressure(false), + mSavedReport(false), + mIsShutdown(false), + mPollingInterval(0) {} + +nsresult nsAvailableMemoryWatcher::Init() { + nsresult rv = nsAvailableMemoryWatcherBase::Init(); + if (NS_FAILED(rv)) { + return rv; + } + + MutexAutoLock lock(mMutex); + mTimer = NS_NewTimer(); + if (!mTimer) { + return NS_ERROR_OUT_OF_MEMORY; + } + + // Use a very short interval for GTest to verify the timer's behavior. + mPollingInterval = gIsGtest ? 10 : 10000; + + if (!RegisterMemoryResourceHandler(lock)) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +nsAvailableMemoryWatcher::~nsAvailableMemoryWatcher() { + // These handles should have been released during the shutdown phase. + MOZ_ASSERT(!mLowMemoryHandle); + MOZ_ASSERT(!mWaitHandle); +} + +// static +VOID CALLBACK nsAvailableMemoryWatcher::LowMemoryCallback(PVOID aContext, + BOOLEAN aIsTimer) { + if (aIsTimer) { + return; + } + + // The |watcher| was addref'ed when we registered the wait handle in + // ListenForLowMemory(). It is decremented when exiting the function, + // so please make sure we unregister the wait handle after this line. + RefPtr<nsAvailableMemoryWatcher> watcher = + already_AddRefed<nsAvailableMemoryWatcher>( + static_cast<nsAvailableMemoryWatcher*>(aContext)); + + MutexAutoLock lock(watcher->mMutex); + if (watcher->mIsShutdown) { + // mWaitHandle should have been unregistered during shutdown + MOZ_ASSERT(!watcher->mWaitHandle); + return; + } + + ::UnregisterWait(watcher->mWaitHandle); + watcher->mWaitHandle = nullptr; + + // On Windows, memory allocations fails when the available commit space is + // not sufficient. It's possible that this callback function is invoked + // but there is still commit space enough for the application to continue + // to run. In such a case, there is no strong need to trigger the memory + // pressure event. So we trigger the event only when the available commit + // space is low. + if (IsCommitSpaceLow()) { + watcher->OnLowMemory(lock); + } else { + // Since we have unregistered the callback, we need to start a timer to + // continue watching memory. + watcher->StartPollingIfUserInteracting(lock); + } +} + +// static +void nsAvailableMemoryWatcher::RecordLowMemoryEvent() { + sNumLowPhysicalMemEvents++; + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::LowPhysicalMemoryEvents, + sNumLowPhysicalMemEvents); +} + +bool nsAvailableMemoryWatcher::RegisterMemoryResourceHandler( + const MutexAutoLock& aLock) { + mLowMemoryHandle.own( + ::CreateMemoryResourceNotification(LowMemoryResourceNotification)); + + if (!mLowMemoryHandle) { + return false; + } + + return ListenForLowMemory(aLock); +} + +void nsAvailableMemoryWatcher::UnregisterMemoryResourceHandler( + const MutexAutoLock&) { + if (mWaitHandle) { + bool res = ::UnregisterWait(mWaitHandle); + if (res || ::GetLastError() != ERROR_IO_PENDING) { + // We decrement the refcount only when we're sure the LowMemoryCallback() + // callback won't be invoked, otherwise the callback will do it + this->Release(); + } + mWaitHandle = nullptr; + } + + mLowMemoryHandle.reset(); +} + +void nsAvailableMemoryWatcher::Shutdown(const MutexAutoLock& aLock) { + mIsShutdown = true; + mNeedToRestartTimerOnUserInteracting = false; + + if (mTimer) { + mTimer->Cancel(); + mTimer = nullptr; + } + + UnregisterMemoryResourceHandler(aLock); +} + +bool nsAvailableMemoryWatcher::ListenForLowMemory(const MutexAutoLock&) { + if (mLowMemoryHandle && !mWaitHandle) { + // We're giving ownership of this object to the LowMemoryCallback(). We + // increment the count here so that the object is kept alive until the + // callback decrements it. + this->AddRef(); + bool res = ::RegisterWaitForSingleObject( + &mWaitHandle, mLowMemoryHandle, LowMemoryCallback, this, INFINITE, + WT_EXECUTEDEFAULT | WT_EXECUTEONLYONCE); + if (!res) { + // We couldn't register the callback, decrement the count + this->Release(); + } + // Once we register the wait handle, we no longer need to start + // the timer because we can continue watching memory via callback. + mNeedToRestartTimerOnUserInteracting = false; + return res; + } + + return false; +} + +void nsAvailableMemoryWatcher::MaybeSaveMemoryReport(const MutexAutoLock&) { + if (mSavedReport) { + return; + } + + if (nsCOMPtr<nsICrashReporter> cr = + do_GetService("@mozilla.org/toolkit/crash-reporter;1")) { + mSavedReport = NS_SUCCEEDED(cr->SaveMemoryReport()); + } +} + +void nsAvailableMemoryWatcher::OnLowMemory(const MutexAutoLock& aLock) { + mUnderMemoryPressure = true; + RecordLowMemoryEvent(); + + if (NS_IsMainThread()) { + MaybeSaveMemoryReport(aLock); + UpdateLowMemoryTimeStamp(); + { + // Don't invoke UnloadTabAsync() with the lock to avoid deadlock + // because nsAvailableMemoryWatcher::Notify may be invoked while + // running the method. + MutexAutoUnlock unlock(mMutex); + mTabUnloader->UnloadTabAsync(); + } + } else { + // SaveMemoryReport and mTabUnloader needs to be run in the main thread + // (See nsMemoryReporterManager::GetReportsForThisProcessExtended) + NS_DispatchToMainThread(NS_NewRunnableFunction( + "nsAvailableMemoryWatcher::OnLowMemory", [self = RefPtr{this}]() { + { + MutexAutoLock lock(self->mMutex); + self->MaybeSaveMemoryReport(lock); + self->UpdateLowMemoryTimeStamp(); + } + self->mTabUnloader->UnloadTabAsync(); + })); + } + + StartPollingIfUserInteracting(aLock); +} + +void nsAvailableMemoryWatcher::OnHighMemory(const MutexAutoLock& aLock) { + MOZ_ASSERT(NS_IsMainThread()); + + if (mUnderMemoryPressure) { + RecordTelemetryEventOnHighMemory(aLock); + NS_NotifyOfEventualMemoryPressure(MemoryPressureState::NoPressure); + } + + mUnderMemoryPressure = false; + mSavedReport = false; // Will save a new report if memory gets low again + StopPolling(aLock); + ListenForLowMemory(aLock); +} + +// static +bool nsAvailableMemoryWatcher::IsCommitSpaceLow() { + // Other options to get the available page file size: + // - GetPerformanceInfo + // Too slow, don't use it. + // - PdhCollectQueryData and PdhGetRawCounterValue + // Faster than GetPerformanceInfo, but slower than GlobalMemoryStatusEx. + // - NtQuerySystemInformation(SystemMemoryUsageInformation) + // Faster than GlobalMemoryStatusEx, but undocumented. + MEMORYSTATUSEX memStatus = {sizeof(memStatus)}; + if (!::GlobalMemoryStatusEx(&memStatus)) { + return false; + } + + constexpr size_t kBytesPerMB = 1024 * 1024; + return (memStatus.ullAvailPageFile / kBytesPerMB) < + StaticPrefs::browser_low_commit_space_threshold_mb(); +} + +void nsAvailableMemoryWatcher::StartPollingIfUserInteracting( + const MutexAutoLock&) { + // When the user is inactive, we mark the flag to delay-start + // the timer when the user becomes active later. + mNeedToRestartTimerOnUserInteracting = true; + + if (mInteracting && !mPolling) { + if (NS_SUCCEEDED(mTimer->InitWithCallback( + this, mPollingInterval, nsITimer::TYPE_REPEATING_SLACK))) { + mPolling = true; + } + } +} + +void nsAvailableMemoryWatcher::StopPolling(const MutexAutoLock&) { + mTimer->Cancel(); + mPolling = false; +} + +void nsAvailableMemoryWatcher::StopPollingIfUserIdle( + const MutexAutoLock& aLock) { + if (!mInteracting) { + StopPolling(aLock); + } +} + +void nsAvailableMemoryWatcher::OnUserInteracting(const MutexAutoLock& aLock) { + if (mNeedToRestartTimerOnUserInteracting) { + StartPollingIfUserInteracting(aLock); + } +} + +// Timer callback, polls the low memory resource notification to detect when +// we've freed enough memory or if we have to send more memory pressure events. +// Polling stops automatically when the user is not interacting with the UI. +NS_IMETHODIMP +nsAvailableMemoryWatcher::Notify(nsITimer* aTimer) { + MutexAutoLock lock(mMutex); + StopPollingIfUserIdle(lock); + + if (IsCommitSpaceLow()) { + OnLowMemory(lock); + } else { + OnHighMemory(lock); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsAvailableMemoryWatcher::GetName(nsACString& aName) { + aName.AssignLiteral("nsAvailableMemoryWatcher"); + return NS_OK; +} + +// Observer service callback, used to stop the polling timer when the user +// stops interacting with Firefox and resuming it when they interact again. +// Also used to shut down the service if the application is quitting. +NS_IMETHODIMP +nsAvailableMemoryWatcher::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + nsresult rv = nsAvailableMemoryWatcherBase::Observe(aSubject, aTopic, aData); + if (NS_FAILED(rv)) { + return rv; + } + + MutexAutoLock lock(mMutex); + + if (strcmp(aTopic, "xpcom-shutdown") == 0) { + Shutdown(lock); + } else if (strcmp(aTopic, "user-interaction-active") == 0) { + OnUserInteracting(lock); + } + + return NS_OK; +} + +already_AddRefed<nsAvailableMemoryWatcherBase> CreateAvailableMemoryWatcher() { + RefPtr watcher(new nsAvailableMemoryWatcher); + if (NS_FAILED(watcher->Init())) { + return do_AddRef(new nsAvailableMemoryWatcherBase); // fallback + } + return watcher.forget(); +} + +} // namespace mozilla diff --git a/xpcom/base/ClearOnShutdown.cpp b/xpcom/base/ClearOnShutdown.cpp new file mode 100644 index 0000000000..700785736d --- /dev/null +++ b/xpcom/base/ClearOnShutdown.cpp @@ -0,0 +1,63 @@ +/* -*- 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 sCurrentClearOnShutdownPhase = ShutdownPhase::NotInShutdown; + +void InsertIntoShutdownList(ShutdownObserver* aObserver, ShutdownPhase aPhase) { + // Adding a ClearOnShutdown for a "past" phase is an error. + if (PastShutdownPhase(aPhase)) { + MOZ_ASSERT(false, "ClearOnShutdown for phase that already was cleared"); + aObserver->Shutdown(); + delete aObserver; + return; + } + + if (!(sShutdownObservers[static_cast<size_t>(aPhase)])) { + sShutdownObservers[static_cast<size_t>(aPhase)] = new ShutdownList(); + } + sShutdownObservers[static_cast<size_t>(aPhase)]->insertBack(aObserver); +} + +} // namespace ClearOnShutdown_Internal + +// Called by AdvanceShutdownPhase each time we switch a phase. Will null out +// pointers added by ClearOnShutdown for all phases up to and including aPhase. +void KillClearOnShutdown(ShutdownPhase aPhase) { + using namespace ClearOnShutdown_Internal; + + MOZ_ASSERT(NS_IsMainThread()); + // Shutdown only goes one direction... + MOZ_ASSERT(!PastShutdownPhase(aPhase)); + + // Set the phase before notifying observers to make sure that they can't run + // any code which isn't allowed to run after the start of this phase. + sCurrentClearOnShutdownPhase = 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)]->popLast()) { + 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 0000000000..f927e3334a --- /dev/null +++ b/xpcom/base/ClearOnShutdown.h @@ -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/. */ + +#ifndef mozilla_ClearOnShutdown_h +#define mozilla_ClearOnShutdown_h + +#include "mozilla/LinkedList.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/Array.h" +#include "ShutdownPhase.h" +#include "MainThreadUtils.h" + +#include <functional> + +/* + * This header exports two public methods in the mozilla namespace: + * + * template<class SmartPtr> + * void ClearOnShutdown(SmartPtr *aPtr, + * aPhase=ShutdownPhase::XPCOMShutdownFinal) + * + * 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, + * RefPtr, 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. + * + * template <typename CallableT> + * void RunOnShutdown(CallableT&& aCallable, + * aPhase = ShutdownPhase::XPCOMShutdownFinal) + * + * This function takes a callable and executes it upon shutdown at the start of + * the specified phase. If the phase has already occurred when RunOnShutdown() + * 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. + * + * ClearOnShutdown and RunOnShutdown are both 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 { + +namespace ClearOnShutdown_Internal { + +class ShutdownObserver : public LinkedListElement<ShutdownObserver> { + public: + virtual void Shutdown() = 0; + virtual ~ShutdownObserver() = default; +}; + +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; +}; + +class FunctionInvoker : public ShutdownObserver { + public: + template <typename CallableT> + explicit FunctionInvoker(CallableT&& aCallable) + : mCallable(std::forward<CallableT>(aCallable)) {} + + virtual void Shutdown() override { + if (!mCallable) { + return; + } + + mCallable(); + } + + private: + std::function<void()> mCallable; +}; + +void InsertIntoShutdownList(ShutdownObserver* aShutdownObserver, + ShutdownPhase aPhase); + +typedef LinkedList<ShutdownObserver> ShutdownList; +extern Array<StaticAutoPtr<ShutdownList>, + static_cast<size_t>(ShutdownPhase::ShutdownPhase_Length)> + sShutdownObservers; +extern ShutdownPhase sCurrentClearOnShutdownPhase; + +} // namespace ClearOnShutdown_Internal + +template <class SmartPtr> +inline void ClearOnShutdown( + SmartPtr* aPtr, ShutdownPhase aPhase = ShutdownPhase::XPCOMShutdownFinal) { + using namespace ClearOnShutdown_Internal; + + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aPhase != ShutdownPhase::ShutdownPhase_Length); + + InsertIntoShutdownList(new PointerClearer<SmartPtr>(aPtr), aPhase); +} + +template <typename CallableT> +inline void RunOnShutdown( + CallableT&& aCallable, + ShutdownPhase aPhase = ShutdownPhase::XPCOMShutdownFinal) { + using namespace ClearOnShutdown_Internal; + + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aPhase != ShutdownPhase::ShutdownPhase_Length); + + InsertIntoShutdownList( + new FunctionInvoker(std::forward<CallableT>(aCallable)), aPhase); +} + +inline bool PastShutdownPhase(ShutdownPhase aPhase) { + MOZ_ASSERT(NS_IsMainThread()); + + return size_t(ClearOnShutdown_Internal::sCurrentClearOnShutdownPhase) >= + size_t(aPhase); +} + +// Called by AdvanceShutdownPhase each time we switch a phase. Will null out +// pointers added by ClearOnShutdown for all phases up to and including aPhase. +void KillClearOnShutdown(ShutdownPhase aPhase); + +} // namespace mozilla + +#endif diff --git a/xpcom/base/CodeAddressService.h b/xpcom/base/CodeAddressService.h new file mode 100644 index 0000000000..821bf2c73c --- /dev/null +++ b/xpcom/base/CodeAddressService.h @@ -0,0 +1,250 @@ +/* -*- 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 <cstddef> +#include <cstdint> +#include <cstring> +#include "mozilla/AllocPolicy.h" +#include "mozilla/Assertions.h" +#include "mozilla/HashFunctions.h" +#include "mozilla/HashTable.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/StackWalk.h" + +namespace mozilla { + +namespace detail { + +template <class AllocPolicy> +class CodeAddressServiceAllocPolicy : public AllocPolicy { + public: + char* strdup_(const char* aStr) { + char* s = AllocPolicy::template pod_malloc<char>(strlen(aStr) + 1); + if (!s) { + MOZ_CRASH("CodeAddressService OOM"); + } + strcpy(s, aStr); + return s; + } +}; + +// Default implementation of DescribeCodeAddressLock. +struct DefaultDescribeCodeAddressLock { + static void Unlock() {} + static void Lock() {} + // Because CodeAddressService asserts that IsLocked() is true, returning true + // here is a sensible default when there is no relevant lock. + static bool IsLocked() { return true; } +}; + +} // namespace detail + +// This class is used to print details about code locations. +// +// |AllocPolicy_| must adhere to the description in mfbt/AllocPolicy.h. +// +// |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 AllocPolicy_ = MallocAllocPolicy, + class DescribeCodeAddressLock = + detail::DefaultDescribeCodeAddressLock> +class CodeAddressService + : private detail::CodeAddressServiceAllocPolicy<AllocPolicy_> { + protected: + // 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. + + using AllocPolicy = detail::CodeAddressServiceAllocPolicy<AllocPolicy_>; + using StringHashSet = HashSet<const char*, CStringHasher, AllocPolicy>; + + StringHashSet mLibraryStrings; + + struct Entry : private AllocPolicy { + 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. + AllocPolicy::free_(mFunction); + AllocPolicy::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. + AllocPolicy::free_(mFunction); + mFunction = !aFunction[0] ? nullptr : AllocPolicy::strdup_(aFunction); + AllocPolicy::free_(mFileName); + mFileName = !aFileName[0] ? nullptr : AllocPolicy::strdup_(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; + } + }; + + const char* InternLibraryString(const char* aString) { + auto p = mLibraryStrings.lookupForAdd(aString); + if (p) { + return *p; + } + + const char* newString = AllocPolicy::strdup_(aString); + if (!mLibraryStrings.add(p, newString)) { + MOZ_CRASH("CodeAddressService OOM"); + } + return newString; + } + + Entry& GetEntry(const void* aPc) { + 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 = InternLibraryString(details.library); + entry.Replace(aPc, details.function, library, details.loffset, + details.filename, details.lineno); + + } else { + mNumCacheHits++; + } + + MOZ_ASSERT(entry.mPc == aPc); + + return entry; + } + + // 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() + : mLibraryStrings(64), mEntries(), mNumCacheHits(0), mNumCacheMisses(0) {} + + ~CodeAddressService() { + for (auto iter = mLibraryStrings.iter(); !iter.done(); iter.next()) { + AllocPolicy::free_(const_cast<char*>(iter.get())); + } + } + + // Returns the minimum number of characters necessary to format the frame + // information, without the terminating null. The buffer will be truncated + // if the returned value is greater than aBufLen-1. + int GetLocation(uint32_t aFrameNumber, const void* aPc, char* aBuf, + size_t aBufLen) { + Entry& entry = GetEntry(aPc); + return 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.shallowSizeOfExcludingThis(aMallocSizeOf); + for (auto iter = mLibraryStrings.iter(); !iter.done(); iter.next()) { + n += aMallocSizeOf(iter.get()); + } + + 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 0000000000..02d74f3223 --- /dev/null +++ b/xpcom/base/CountingAllocatorBase.h @@ -0,0 +1,158 @@ +/* -*- 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 <cstdlib> +#include "mozilla/Assertions.h" +#include "mozilla/Atomics.h" +#include "mozilla/mozalloc.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); + } + + // Infallible-allocation wrappers for the counting malloc/calloc/realloc + // functions, for clients that don't safely handle allocation failures + // themselves. + static void* InfallibleCountingMalloc(size_t size) { + void* p = moz_xmalloc(size); + sAmount += MallocSizeOfOnAlloc(p); + return p; + } + + static void* InfallibleCountingCalloc(size_t nmemb, size_t size) { + void* p = moz_xcalloc(nmemb, size); + sAmount += MallocSizeOfOnAlloc(p); + return p; + } + + static void* InfallibleCountingRealloc(void* p, size_t size) { + size_t oldsize = MallocSizeOfOnFree(p); + void* pnew = moz_xrealloc(p, size); + if (pnew) { + size_t newsize = MallocSizeOfOnAlloc(pnew); + sAmount += newsize - oldsize; + } else if (size == 0) { + // See comment in CountingRealloc above. + sAmount -= oldsize; + } else { + // realloc failed. The amount allocated hasn't changed. + } + return pnew; + } + + private: + // |sAmount| can be (implicitly) accessed by multiple threads, so it + // must be thread-safe. It may be written during GC, so accesses are not + // recorded. + typedef Atomic<size_t, SequentiallyConsistent> AmountType; + static inline AmountType sAmount{0}; + + 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 0000000000..b65acebe2e --- /dev/null +++ b/xpcom/base/CycleCollectedJSContext.cpp @@ -0,0 +1,925 @@ +/* -*- 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/CycleCollectedJSContext.h" + +#include <algorithm> +#include <utility> + +#include "js/Debug.h" +#include "js/GCAPI.h" +#include "js/Utility.h" +#include "jsapi.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/AsyncEventDispatcher.h" +#include "mozilla/AutoRestore.h" +#include "mozilla/CycleCollectedJSRuntime.h" +#include "mozilla/DebuggerOnGCRunnable.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/ProfilerMarkers.h" +#include "mozilla/Sprintf.h" +#include "mozilla/Telemetry.h" +#include "mozilla/Unused.h" +#include "mozilla/dom/DOMException.h" +#include "mozilla/dom/DOMJSClass.h" +#include "mozilla/dom/FinalizationRegistryBinding.h" +#include "mozilla/dom/PromiseBinding.h" +#include "mozilla/dom/PromiseDebugging.h" +#include "mozilla/dom/PromiseRejectionEvent.h" +#include "mozilla/dom/PromiseRejectionEventBinding.h" +#include "mozilla/dom/RootedDictionary.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/dom/UserActivation.h" +#include "nsContentUtils.h" +#include "nsCycleCollectionNoteRootCallback.h" +#include "nsCycleCollectionParticipant.h" +#include "nsCycleCollector.h" +#include "nsDOMJSUtils.h" +#include "nsDOMMutationObserver.h" +#include "nsJSUtils.h" +#include "nsPIDOMWindow.h" +#include "nsStringBuffer.h" +#include "nsThread.h" +#include "nsThreadUtils.h" +#include "nsWrapperCache.h" +#include "xpcpublic.h" + +using namespace mozilla; +using namespace mozilla::dom; + +namespace mozilla { + +CycleCollectedJSContext::CycleCollectedJSContext() + : mRuntime(nullptr), + mJSContext(nullptr), + mDoingStableStates(false), + mTargetedMicroTaskRecursionDepth(0), + mMicroTaskLevel(0), + mSuppressionGeneration(0), + mDebuggerRecursionDepth(0), + mMicroTaskRecursionDepth(0), + mFinalizationRegistryCleanup(this) { + MOZ_COUNT_CTOR(CycleCollectedJSContext); + + nsCOMPtr<nsIThread> thread = do_GetCurrentThread(); + mOwningThread = thread.forget().downcast<nsThread>().take(); + MOZ_RELEASE_ASSERT(mOwningThread); +} + +CycleCollectedJSContext::~CycleCollectedJSContext() { + MOZ_COUNT_DTOR(CycleCollectedJSContext); + // If the allocation failed, here we are. + if (!mJSContext) { + return; + } + + JS::SetHostCleanupFinalizationRegistryCallback(mJSContext, nullptr, nullptr); + + JS_SetContextPrivate(mJSContext, nullptr); + + mRuntime->SetContext(nullptr); + mRuntime->Shutdown(mJSContext); + + // Last chance to process any events. + CleanupIDBTransactions(mBaseRecursionDepth); + MOZ_ASSERT(mPendingIDBTransactions.IsEmpty()); + + ProcessStableStateQueue(); + MOZ_ASSERT(mStableStateEvents.IsEmpty()); + + // Clear mPendingException first, since it might be cycle collected. + mPendingException = nullptr; + + MOZ_ASSERT(mDebuggerMicroTaskQueue.empty()); + MOZ_ASSERT(mPendingMicroTaskRunnables.empty()); + + mUncaughtRejections.reset(); + mConsumedRejections.reset(); + + mAboutToBeNotifiedRejectedPromises.Clear(); + mPendingUnhandledRejections.Clear(); + + mFinalizationRegistryCleanup.Destroy(); + + JS_DestroyContext(mJSContext); + mJSContext = nullptr; + + nsCycleCollector_forgetJSContext(); + + mozilla::dom::DestroyScriptSettings(); + + mOwningThread->SetScriptObserver(nullptr); + NS_RELEASE(mOwningThread); + + delete mRuntime; + mRuntime = nullptr; +} + +nsresult CycleCollectedJSContext::Initialize(JSRuntime* aParentRuntime, + uint32_t aMaxBytes) { + MOZ_ASSERT(!mJSContext); + + mozilla::dom::InitScriptSettings(); + mJSContext = JS_NewContext(aMaxBytes, aParentRuntime); + if (!mJSContext) { + return NS_ERROR_OUT_OF_MEMORY; + } + + mRuntime = CreateRuntime(mJSContext); + mRuntime->SetContext(this); + + mOwningThread->SetScriptObserver(this); + // The main thread has a base recursion depth of 0, workers of 1. + mBaseRecursionDepth = RecursionDepth(); + + NS_GetCurrentThread()->SetCanInvokeJS(true); + + JS::SetJobQueue(mJSContext, 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())); + + mFinalizationRegistryCleanup.Init(); + + // Cast to PerThreadAtomCache for dom::GetAtomCache(JSContext*). + JS_SetContextPrivate(mJSContext, static_cast<PerThreadAtomCache*>(this)); + + nsCycleCollector_registerJSContext(this); + + return NS_OK; +} + +/* static */ +CycleCollectedJSContext* CycleCollectedJSContext::GetFor(JSContext* aCx) { + // Cast from void* matching JS_SetContextPrivate. + auto atomCache = static_cast<PerThreadAtomCache*>(JS_GetContextPrivate(aCx)); + // Down cast. + return static_cast<CycleCollectedJSContext*>(atomCache); +} + +size_t CycleCollectedJSContext::SizeOfExcludingThis( + MallocSizeOf aMallocSizeOf) const { + return 0; +} + +class PromiseJobRunnable final : public MicroTaskRunnable { + public: + PromiseJobRunnable(JS::HandleObject aPromise, JS::HandleObject aCallback, + JS::HandleObject aCallbackGlobal, + JS::HandleObject aAllocationSite, + nsIGlobalObject* aIncumbentGlobal) + : mCallback(new PromiseJobCallback(aCallback, aCallbackGlobal, + aAllocationSite, aIncumbentGlobal)), + mPropagateUserInputEventHandling(false) { + MOZ_ASSERT(js::IsFunctionObject(aCallback)); + + if (aPromise) { + JS::PromiseUserInputEventHandlingState state = + JS::GetPromiseUserInputEventHandlingState(aPromise); + mPropagateUserInputEventHandling = + state == + JS::PromiseUserInputEventHandlingState::HadUserInteractionAtCreation; + } + } + + virtual ~PromiseJobRunnable() = default; + + protected: + MOZ_CAN_RUN_SCRIPT + virtual void Run(AutoSlowOperation& aAso) override { + JSObject* callback = mCallback->CallbackPreserveColor(); + nsIGlobalObject* global = callback ? xpc::NativeGlobal(callback) : nullptr; + if (global && !global->IsDying()) { + // Propagate the user input event handling bit if needed. + nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(global); + RefPtr<Document> doc; + if (win) { + doc = win->GetExtantDoc(); + } + AutoHandlingUserInputStatePusher userInpStatePusher( + mPropagateUserInputEventHandling); + + mCallback->Call("promise callback"); + aAso.CheckForInterrupt(); + } + // Now that mCallback is no longer needed, clear any pointers it contains to + // JS GC things. This removes any storebuffer entries associated with those + // pointers, which can cause problems by taking up memory and by triggering + // minor GCs. This otherwise would not happen until the next minor GC or + // cycle collection. + mCallback->Reset(); + } + + virtual bool Suppressed() override { + JSObject* callback = mCallback->CallbackPreserveColor(); + nsIGlobalObject* global = callback ? xpc::NativeGlobal(callback) : nullptr; + return global && global->IsInSyncOperation(); + } + + private: + const RefPtr<PromiseJobCallback> mCallback; + bool mPropagateUserInputEventHandling; +}; + +JSObject* CycleCollectedJSContext::getIncumbentGlobal(JSContext* aCx) { + nsIGlobalObject* global = mozilla::dom::GetIncumbentGlobal(); + if (global) { + return global->GetGlobalJSObject(); + } + return nullptr; +} + +bool CycleCollectedJSContext::enqueuePromiseJob( + JSContext* aCx, JS::HandleObject aPromise, JS::HandleObject aJob, + JS::HandleObject aAllocationSite, JS::HandleObject aIncumbentGlobal) { + MOZ_ASSERT(aCx == Context()); + MOZ_ASSERT(Get() == this); + + nsIGlobalObject* global = nullptr; + if (aIncumbentGlobal) { + global = xpc::NativeGlobal(aIncumbentGlobal); + } + JS::RootedObject jobGlobal(aCx, JS::CurrentGlobalOrNull(aCx)); + RefPtr<PromiseJobRunnable> runnable = new PromiseJobRunnable( + aPromise, aJob, jobGlobal, aAllocationSite, global); + DispatchToMicroTask(runnable.forget()); + return true; +} + +// Used only by the SpiderMonkey Debugger API, and even then only via +// JS::AutoDebuggerJobQueueInterruption, to ensure that the debuggee's queue is +// not affected; see comments in js/public/Promise.h. +void CycleCollectedJSContext::runJobs(JSContext* aCx) { + MOZ_ASSERT(aCx == Context()); + MOZ_ASSERT(Get() == this); + PerformMicroTaskCheckPoint(); +} + +bool CycleCollectedJSContext::empty() const { + // This is our override of JS::JobQueue::empty. Since that interface is only + // concerned with the ordinary microtask queue, not the debugger microtask + // queue, we only report on the former. + return mPendingMicroTaskRunnables.empty(); +} + +// Preserve a debuggee's microtask queue while it is interrupted by the +// debugger. See the comments for JS::AutoDebuggerJobQueueInterruption. +class CycleCollectedJSContext::SavedMicroTaskQueue + : public JS::JobQueue::SavedJobQueue { + public: + explicit SavedMicroTaskQueue(CycleCollectedJSContext* ccjs) : ccjs(ccjs) { + ccjs->mDebuggerRecursionDepth++; + ccjs->mPendingMicroTaskRunnables.swap(mQueue); + } + + ~SavedMicroTaskQueue() { + // The JS Debugger attempts to maintain the invariant that microtasks which + // occur durring debugger operation are completely flushed from the task + // queue before returning control to the debuggee, in order to avoid + // micro-tasks generated during debugging from interfering with regular + // operation. + // + // While the vast majority of microtasks can be reliably flushed, + // synchronous operations (see nsAutoSyncOperation) such as printing and + // alert diaglogs suppress the execution of some microtasks. + // + // When PerformMicroTaskCheckpoint is run while microtasks are suppressed, + // any suppressed microtasks are gathered into a new SuppressedMicroTasks + // runnable, which is enqueued on exit from PerformMicroTaskCheckpoint. As a + // result, AutoDebuggerJobQueueInterruption::runJobs is not able to + // correctly guarantee that the microtask queue is totally empty in the + // presence of sync operations. + // + // Previous versions of this code release-asserted that the queue was empty, + // causing user observable crashes (Bug 1849675). To avoid this, we instead + // choose to move suspended microtasks from the SavedMicroTaskQueue to the + // main microtask queue in this destructor. This means that jobs enqueued + // during synchnronous events under debugger control may produce events + // which run outside the debugger, but this is viewed as strictly + // preferrable to crashing. + MOZ_RELEASE_ASSERT(ccjs->mPendingMicroTaskRunnables.size() <= 1); + MOZ_RELEASE_ASSERT(ccjs->mDebuggerRecursionDepth); + RefPtr<MicroTaskRunnable> maybeSuppressedTasks; + + // Handle the case where there is a SuppressedMicroTask still in the queue. + if (!ccjs->mPendingMicroTaskRunnables.empty()) { + maybeSuppressedTasks = ccjs->mPendingMicroTaskRunnables.front(); + ccjs->mPendingMicroTaskRunnables.pop_front(); + } + + MOZ_RELEASE_ASSERT(ccjs->mPendingMicroTaskRunnables.empty()); + ccjs->mDebuggerRecursionDepth--; + ccjs->mPendingMicroTaskRunnables.swap(mQueue); + + // Re-enqueue the suppressed task now that we've put the original microtask + // queue back. + if (maybeSuppressedTasks) { + ccjs->mPendingMicroTaskRunnables.push_back(maybeSuppressedTasks); + } + } + + private: + CycleCollectedJSContext* ccjs; + std::deque<RefPtr<MicroTaskRunnable>> mQueue; +}; + +js::UniquePtr<JS::JobQueue::SavedJobQueue> +CycleCollectedJSContext::saveJobQueue(JSContext* cx) { + auto saved = js::MakeUnique<SavedMicroTaskQueue>(this); + if (!saved) { + // When MakeUnique's allocation fails, the SavedMicroTaskQueue constructor + // is never called, so mPendingMicroTaskRunnables is still initialized. + JS_ReportOutOfMemory(cx); + return nullptr; + } + + return saved; +} + +/* static */ +void CycleCollectedJSContext::PromiseRejectionTrackerCallback( + JSContext* aCx, bool aMutedErrors, JS::HandleObject aPromise, + JS::PromiseRejectionHandlingState state, void* aData) { + CycleCollectedJSContext* self = static_cast<CycleCollectedJSContext*>(aData); + + MOZ_ASSERT(aCx == self->Context()); + MOZ_ASSERT(Get() == self); + + // TODO: Bug 1549351 - Promise rejection event should not be sent for + // cross-origin scripts + + PromiseArray& aboutToBeNotified = self->mAboutToBeNotifiedRejectedPromises; + PromiseHashtable& unhandled = self->mPendingUnhandledRejections; + uint64_t promiseID = JS::GetPromiseID(aPromise); + + if (state == JS::PromiseRejectionHandlingState::Unhandled) { + PromiseDebugging::AddUncaughtRejection(aPromise); + if (!aMutedErrors) { + RefPtr<Promise> promise = + Promise::CreateFromExisting(xpc::NativeGlobal(aPromise), aPromise); + aboutToBeNotified.AppendElement(promise); + unhandled.InsertOrUpdate(promiseID, std::move(promise)); + } + } else { + PromiseDebugging::AddConsumedRejection(aPromise); + for (size_t i = 0; i < aboutToBeNotified.Length(); i++) { + if (aboutToBeNotified[i] && + aboutToBeNotified[i]->PromiseObj() == aPromise) { + // To avoid large amounts of memmoves, we don't shrink the vector + // here. Instead, we filter out nullptrs when iterating over the + // vector later. + aboutToBeNotified[i] = nullptr; + DebugOnly<bool> isFound = unhandled.Remove(promiseID); + MOZ_ASSERT(isFound); + return; + } + } + RefPtr<Promise> promise; + unhandled.Remove(promiseID, getter_AddRefs(promise)); + if (!promise && !aMutedErrors) { + nsIGlobalObject* global = xpc::NativeGlobal(aPromise); + if (nsCOMPtr<EventTarget> owner = do_QueryInterface(global)) { + RootedDictionary<PromiseRejectionEventInit> init(aCx); + init.mPromise = Promise::CreateFromExisting(global, aPromise); + init.mReason = JS::GetPromiseResult(aPromise); + + RefPtr<PromiseRejectionEvent> event = + PromiseRejectionEvent::Constructor(owner, u"rejectionhandled"_ns, + init); + + RefPtr<AsyncEventDispatcher> asyncDispatcher = + new AsyncEventDispatcher(owner, event.forget()); + asyncDispatcher->PostDOMEvent(); + } + } + } +} + +already_AddRefed<Exception> CycleCollectedJSContext::GetPendingException() + const { + MOZ_ASSERT(mJSContext); + + nsCOMPtr<Exception> out = mPendingException; + return out.forget(); +} + +void CycleCollectedJSContext::SetPendingException(Exception* aException) { + MOZ_ASSERT(mJSContext); + mPendingException = aException; +} + +std::deque<RefPtr<MicroTaskRunnable>>& +CycleCollectedJSContext::GetMicroTaskQueue() { + MOZ_ASSERT(mJSContext); + return mPendingMicroTaskRunnables; +} + +std::deque<RefPtr<MicroTaskRunnable>>& +CycleCollectedJSContext::GetDebuggerMicroTaskQueue() { + MOZ_ASSERT(mJSContext); + return mDebuggerMicroTaskQueue; +} + +void CycleCollectedJSContext::ProcessStableStateQueue() { + MOZ_ASSERT(mJSContext); + MOZ_RELEASE_ASSERT(!mDoingStableStates); + mDoingStableStates = true; + + // When run, one event can add another event to the mStableStateEvents, as + // such you can't use iterators here. + for (uint32_t i = 0; i < mStableStateEvents.Length(); ++i) { + nsCOMPtr<nsIRunnable> event = std::move(mStableStateEvents[i]); + event->Run(); + } + + mStableStateEvents.Clear(); + mDoingStableStates = false; +} + +void CycleCollectedJSContext::CleanupIDBTransactions(uint32_t aRecursionDepth) { + MOZ_ASSERT(mJSContext); + MOZ_RELEASE_ASSERT(!mDoingStableStates); + mDoingStableStates = true; + + nsTArray<PendingIDBTransactionData> localQueue = + std::move(mPendingIDBTransactions); + + localQueue.RemoveLastElements( + localQueue.end() - + std::remove_if(localQueue.begin(), localQueue.end(), + [aRecursionDepth](PendingIDBTransactionData& data) { + if (data.mRecursionDepth != aRecursionDepth) { + return false; + } + + { + nsCOMPtr<nsIRunnable> transaction = + std::move(data.mTransaction); + transaction->Run(); + } + + return true; + })); + + // If mPendingIDBTransactions has events in it now, they were added from + // something we called, so they belong at the end of the queue. + localQueue.AppendElements(std::move(mPendingIDBTransactions)); + mPendingIDBTransactions = std::move(localQueue); + mDoingStableStates = false; +} + +void CycleCollectedJSContext::BeforeProcessTask(bool aMightBlock) { + // If ProcessNextEvent was called during a microtask callback, we + // must process any pending microtasks before blocking in the event loop, + // otherwise we may deadlock until an event enters the queue later. + if (aMightBlock && PerformMicroTaskCheckPoint()) { + // If any microtask was processed, we post a dummy event in order to + // force the ProcessNextEvent call not to block. This is required + // to support nested event loops implemented using a pattern like + // "while (condition) thread.processNextEvent(true)", in case the + // condition is triggered here by a Promise "then" callback. + NS_DispatchToMainThread(new Runnable("BeforeProcessTask")); + } +} + +void CycleCollectedJSContext::AfterProcessTask(uint32_t aRecursionDepth) { + MOZ_ASSERT(mJSContext); + + // See HTML 6.1.4.2 Processing model + + // Step 4.1: Execute microtasks. + PerformMicroTaskCheckPoint(); + + // Step 4.2 Execute any events that were waiting for a stable state. + ProcessStableStateQueue(); + + // This should be a fast test so that it won't affect the next task + // processing. + MaybePokeGC(); +} + +void CycleCollectedJSContext::AfterProcessMicrotasks() { + MOZ_ASSERT(mJSContext); + // Notify unhandled promise rejections: + // https://html.spec.whatwg.org/multipage/webappapis.html#notify-about-rejected-promises + if (mAboutToBeNotifiedRejectedPromises.Length()) { + RefPtr<NotifyUnhandledRejections> runnable = new NotifyUnhandledRejections( + std::move(mAboutToBeNotifiedRejectedPromises)); + NS_DispatchToCurrentThread(runnable); + } + // Cleanup Indexed Database transactions: + // https://html.spec.whatwg.org/multipage/webappapis.html#perform-a-microtask-checkpoint + CleanupIDBTransactions(RecursionDepth()); + + // Clear kept alive objects in JS WeakRef. + // https://whatpr.org/html/4571/webappapis.html#perform-a-microtask-checkpoint + // + // ECMAScript implementations are expected to call ClearKeptObjects when a + // synchronous sequence of ECMAScript execution completes. + // + // https://tc39.es/proposal-weakrefs/#sec-clear-kept-objects + JS::ClearKeptObjects(mJSContext); +} + +void CycleCollectedJSContext::MaybePokeGC() { + // Worker-compatible check to see if we want to do an idle-time minor + // GC. + class IdleTimeGCTaskRunnable : public mozilla::IdleRunnable { + public: + using mozilla::IdleRunnable::IdleRunnable; + + public: + IdleTimeGCTaskRunnable() : IdleRunnable("IdleTimeGCTask") {} + + NS_IMETHOD Run() override { + CycleCollectedJSRuntime* ccrt = CycleCollectedJSRuntime::Get(); + if (ccrt) { + ccrt->RunIdleTimeGCTask(); + } + return NS_OK; + } + }; + + if (Runtime()->IsIdleGCTaskNeeded()) { + nsCOMPtr<nsIRunnable> gc_task = new IdleTimeGCTaskRunnable(); + NS_DispatchToCurrentThreadQueue(gc_task.forget(), EventQueuePriority::Idle); + Runtime()->SetPendingIdleGCTask(); + } +} + +uint32_t CycleCollectedJSContext::RecursionDepth() const { + // Debugger interruptions are included in the recursion depth so that debugger + // microtask checkpoints do not run IDB transactions which were initiated + // before the interruption. + return mOwningThread->RecursionDepth() + mDebuggerRecursionDepth; +} + +void CycleCollectedJSContext::RunInStableState( + already_AddRefed<nsIRunnable>&& aRunnable) { + MOZ_ASSERT(mJSContext); + mStableStateEvents.AppendElement(std::move(aRunnable)); +} + +void CycleCollectedJSContext::AddPendingIDBTransaction( + already_AddRefed<nsIRunnable>&& aTransaction) { + MOZ_ASSERT(mJSContext); + + PendingIDBTransactionData data; + data.mTransaction = aTransaction; + + 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 + + mPendingIDBTransactions.AppendElement(std::move(data)); +} + +void CycleCollectedJSContext::DispatchToMicroTask( + already_AddRefed<MicroTaskRunnable> aRunnable) { + RefPtr<MicroTaskRunnable> runnable(aRunnable); + + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(runnable); + + JS::JobQueueMayNotBeEmpty(Context()); + + LogMicroTaskRunnable::LogDispatch(runnable.get()); + mPendingMicroTaskRunnables.push_back(std::move(runnable)); +} + +class AsyncMutationHandler final : public mozilla::Runnable { + public: + AsyncMutationHandler() : mozilla::Runnable("AsyncMutationHandler") {} + + // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT. See + // bug 1535398. + MOZ_CAN_RUN_SCRIPT_BOUNDARY + NS_IMETHOD Run() override { + CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get(); + if (ccjs) { + ccjs->PerformMicroTaskCheckPoint(); + } + return NS_OK; + } +}; + +SuppressedMicroTasks::SuppressedMicroTasks(CycleCollectedJSContext* aContext) + : mContext(aContext), + mSuppressionGeneration(aContext->mSuppressionGeneration) {} + +bool SuppressedMicroTasks::Suppressed() { + if (mSuppressionGeneration == mContext->mSuppressionGeneration) { + return true; + } + + for (std::deque<RefPtr<MicroTaskRunnable>>::reverse_iterator it = + mSuppressedMicroTaskRunnables.rbegin(); + it != mSuppressedMicroTaskRunnables.rend(); ++it) { + mContext->GetMicroTaskQueue().push_front(*it); + } + mContext->mSuppressedMicroTasks = nullptr; + + return false; +} + +bool CycleCollectedJSContext::PerformMicroTaskCheckPoint(bool aForce) { + if (mPendingMicroTaskRunnables.empty() && mDebuggerMicroTaskQueue.empty()) { + AfterProcessMicrotasks(); + // Nothing to do, return early. + return false; + } + + uint32_t currentDepth = RecursionDepth(); + if (mMicroTaskRecursionDepth >= currentDepth && !aForce) { + // We are already executing microtasks for the current recursion depth. + return false; + } + + if (mTargetedMicroTaskRecursionDepth != 0 && + mTargetedMicroTaskRecursionDepth + mDebuggerRecursionDepth != + currentDepth) { + return false; + } + + if (NS_IsMainThread() && !nsContentUtils::IsSafeToRunScript()) { + // Special case for main thread where DOM mutations may happen when + // it is not safe to run scripts. + nsContentUtils::AddScriptRunner(new AsyncMutationHandler()); + return false; + } + + mozilla::AutoRestore<uint32_t> restore(mMicroTaskRecursionDepth); + MOZ_ASSERT(aForce ? currentDepth == 0 : currentDepth > 0); + mMicroTaskRecursionDepth = currentDepth; + + AUTO_PROFILER_TRACING_MARKER("JS", "Perform microtasks", JS); + + bool didProcess = false; + AutoSlowOperation aso; + + for (;;) { + RefPtr<MicroTaskRunnable> runnable; + if (!mDebuggerMicroTaskQueue.empty()) { + runnable = std::move(mDebuggerMicroTaskQueue.front()); + mDebuggerMicroTaskQueue.pop_front(); + } else if (!mPendingMicroTaskRunnables.empty()) { + runnable = std::move(mPendingMicroTaskRunnables.front()); + mPendingMicroTaskRunnables.pop_front(); + } else { + break; + } + + if (runnable->Suppressed()) { + // Microtasks in worker shall never be suppressed. + // Otherwise, mPendingMicroTaskRunnables will be replaced later with + // all suppressed tasks in mDebuggerMicroTaskQueue unexpectedly. + MOZ_ASSERT(NS_IsMainThread()); + JS::JobQueueMayNotBeEmpty(Context()); + if (runnable != mSuppressedMicroTasks) { + if (!mSuppressedMicroTasks) { + mSuppressedMicroTasks = new SuppressedMicroTasks(this); + } + mSuppressedMicroTasks->mSuppressedMicroTaskRunnables.push_back( + runnable); + } + } else { + if (mPendingMicroTaskRunnables.empty() && + mDebuggerMicroTaskQueue.empty() && !mSuppressedMicroTasks) { + JS::JobQueueIsEmpty(Context()); + } + didProcess = true; + + LogMicroTaskRunnable::Run log(runnable.get()); + runnable->Run(aso); + runnable = nullptr; + } + } + + // Put back the suppressed microtasks so that they will be run later. + // Note, it is possible that we end up keeping these suppressed tasks around + // for some time, but no longer than spinning the event loop nestedly + // (sync XHR, alert, etc.) + if (mSuppressedMicroTasks) { + mPendingMicroTaskRunnables.push_back(mSuppressedMicroTasks); + } + + AfterProcessMicrotasks(); + + return didProcess; +} + +void CycleCollectedJSContext::PerformDebuggerMicroTaskCheckpoint() { + // Don't do normal microtask handling checks here, since whoever is calling + // this method is supposed to know what they are doing. + + AutoSlowOperation aso; + for (;;) { + // For a debugger microtask checkpoint, we always use the debugger microtask + // queue. + std::deque<RefPtr<MicroTaskRunnable>>* microtaskQueue = + &GetDebuggerMicroTaskQueue(); + + if (microtaskQueue->empty()) { + break; + } + + RefPtr<MicroTaskRunnable> runnable = std::move(microtaskQueue->front()); + MOZ_ASSERT(runnable); + + LogMicroTaskRunnable::Run log(runnable.get()); + + // This function can re-enter, so we remove the element before calling. + microtaskQueue->pop_front(); + + if (mPendingMicroTaskRunnables.empty() && mDebuggerMicroTaskQueue.empty()) { + JS::JobQueueIsEmpty(Context()); + } + runnable->Run(aso); + runnable = nullptr; + } + + AfterProcessMicrotasks(); +} + +NS_IMETHODIMP CycleCollectedJSContext::NotifyUnhandledRejections::Run() { + for (size_t i = 0; i < mUnhandledRejections.Length(); ++i) { + CycleCollectedJSContext* cccx = CycleCollectedJSContext::Get(); + NS_ENSURE_STATE(cccx); + + RefPtr<Promise>& promise = mUnhandledRejections[i]; + if (!promise) { + continue; + } + + JS::RootingContext* cx = cccx->RootingCx(); + JS::RootedObject promiseObj(cx, promise->PromiseObj()); + MOZ_ASSERT(JS::IsPromiseObject(promiseObj)); + + // Only fire unhandledrejection if the promise is still not handled; + uint64_t promiseID = JS::GetPromiseID(promiseObj); + if (!JS::GetPromiseIsHandled(promiseObj)) { + if (nsCOMPtr<EventTarget> target = + do_QueryInterface(promise->GetParentObject())) { + RootedDictionary<PromiseRejectionEventInit> init(cx); + init.mPromise = promise; + init.mReason = JS::GetPromiseResult(promiseObj); + init.mCancelable = true; + + RefPtr<PromiseRejectionEvent> event = + PromiseRejectionEvent::Constructor(target, u"unhandledrejection"_ns, + init); + // We don't use the result of dispatching event here to check whether to + // report the Promise to console. + target->DispatchEvent(*event); + } + } + + cccx = CycleCollectedJSContext::Get(); + NS_ENSURE_STATE(cccx); + if (!JS::GetPromiseIsHandled(promiseObj)) { + DebugOnly<bool> isFound = + cccx->mPendingUnhandledRejections.Remove(promiseID); + MOZ_ASSERT(isFound); + } + + // If a rejected promise is being handled in "unhandledrejection" event + // handler, it should be removed from the table in + // PromiseRejectionTrackerCallback. + MOZ_ASSERT(!cccx->mPendingUnhandledRejections.Lookup(promiseID)); + } + return NS_OK; +} + +nsresult CycleCollectedJSContext::NotifyUnhandledRejections::Cancel() { + CycleCollectedJSContext* cccx = CycleCollectedJSContext::Get(); + NS_ENSURE_STATE(cccx); + + for (size_t i = 0; i < mUnhandledRejections.Length(); ++i) { + RefPtr<Promise>& promise = mUnhandledRejections[i]; + if (!promise) { + continue; + } + + JS::RootedObject promiseObj(cccx->RootingCx(), promise->PromiseObj()); + cccx->mPendingUnhandledRejections.Remove(JS::GetPromiseID(promiseObj)); + } + return NS_OK; +} + +class FinalizationRegistryCleanup::CleanupRunnable + : public DiscardableRunnable { + public: + explicit CleanupRunnable(FinalizationRegistryCleanup* aCleanupWork) + : DiscardableRunnable("CleanupRunnable"), mCleanupWork(aCleanupWork) {} + + // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT. See + // bug 1535398. + MOZ_CAN_RUN_SCRIPT_BOUNDARY + NS_IMETHOD Run() override { + mCleanupWork->DoCleanup(); + return NS_OK; + } + + private: + FinalizationRegistryCleanup* mCleanupWork; +}; + +FinalizationRegistryCleanup::FinalizationRegistryCleanup( + CycleCollectedJSContext* aContext) + : mContext(aContext) {} + +void FinalizationRegistryCleanup::Destroy() { + // This must happen before the CycleCollectedJSContext destructor calls + // JS_DestroyContext(). + mCallbacks.reset(); +} + +void FinalizationRegistryCleanup::Init() { + JSContext* cx = mContext->Context(); + mCallbacks.init(cx); + JS::SetHostCleanupFinalizationRegistryCallback(cx, QueueCallback, this); +} + +/* static */ +void FinalizationRegistryCleanup::QueueCallback(JSFunction* aDoCleanup, + JSObject* aIncumbentGlobal, + void* aData) { + FinalizationRegistryCleanup* cleanup = + static_cast<FinalizationRegistryCleanup*>(aData); + cleanup->QueueCallback(aDoCleanup, aIncumbentGlobal); +} + +void FinalizationRegistryCleanup::QueueCallback(JSFunction* aDoCleanup, + JSObject* aIncumbentGlobal) { + bool firstCallback = mCallbacks.empty(); + + MOZ_ALWAYS_TRUE(mCallbacks.append(Callback{aDoCleanup, aIncumbentGlobal})); + + if (firstCallback) { + RefPtr<CleanupRunnable> cleanup = new CleanupRunnable(this); + NS_DispatchToCurrentThread(cleanup.forget()); + } +} + +void FinalizationRegistryCleanup::DoCleanup() { + if (mCallbacks.empty()) { + return; + } + + JS::RootingContext* cx = mContext->RootingCx(); + + JS::Rooted<CallbackVector> callbacks(cx); + std::swap(callbacks.get(), mCallbacks.get()); + + for (const Callback& callback : callbacks) { + JS::ExposeObjectToActiveJS( + JS_GetFunctionObject(callback.mCallbackFunction)); + JS::ExposeObjectToActiveJS(callback.mIncumbentGlobal); + + JS::RootedObject functionObj( + cx, JS_GetFunctionObject(callback.mCallbackFunction)); + JS::RootedObject globalObj(cx, JS::GetNonCCWObjectGlobal(functionObj)); + + nsIGlobalObject* incumbentGlobal = + xpc::NativeGlobal(callback.mIncumbentGlobal); + if (!incumbentGlobal) { + continue; + } + + RefPtr<FinalizationRegistryCleanupCallback> cleanupCallback( + new FinalizationRegistryCleanupCallback(functionObj, globalObj, nullptr, + incumbentGlobal)); + + nsIGlobalObject* global = + xpc::NativeGlobal(cleanupCallback->CallbackPreserveColor()); + if (global) { + cleanupCallback->Call("FinalizationRegistryCleanup::DoCleanup"); + } + } +} + +void FinalizationRegistryCleanup::Callback::trace(JSTracer* trc) { + JS::TraceRoot(trc, &mCallbackFunction, "mCallbackFunction"); + JS::TraceRoot(trc, &mIncumbentGlobal, "mIncumbentGlobal"); +} + +} // namespace mozilla diff --git a/xpcom/base/CycleCollectedJSContext.h b/xpcom/base/CycleCollectedJSContext.h new file mode 100644 index 0000000000..bbe47a57a5 --- /dev/null +++ b/xpcom/base/CycleCollectedJSContext.h @@ -0,0 +1,396 @@ +/* -*- 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 <deque> + +#include "mozilla/Attributes.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/dom/AtomList.h" +#include "mozilla/dom/Promise.h" +#include "js/GCVector.h" +#include "js/Promise.h" + +#include "nsCOMPtr.h" +#include "nsRefPtrHashtable.h" +#include "nsTArray.h" + +class nsCycleCollectionNoteRootCallback; +class nsIRunnable; +class nsThread; + +namespace mozilla { +class AutoSlowOperation; + +class CycleCollectedJSContext; +class CycleCollectedJSRuntime; + +namespace dom { +class Exception; +class WorkerJSContext; +class WorkletJSContext; +} // namespace dom + +// 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; + mSuspectedAtCCStart = 0; + 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; + // mAnyManual is true if any slice was manually triggered, and at shutdown. + bool mAnyManual; + uint32_t mSuspectedAtCCStart; + uint32_t mVisitedRefCounted; + uint32_t mVisitedGCed; + uint32_t mFreedRefCounted; + uint32_t mFreedGCed; + uint32_t mFreedJSZones; + uint32_t mNumSlices; +}; + +class MicroTaskRunnable { + public: + MicroTaskRunnable() = default; + NS_INLINE_DECL_REFCOUNTING(MicroTaskRunnable) + MOZ_CAN_RUN_SCRIPT virtual void Run(AutoSlowOperation& aAso) = 0; + virtual bool Suppressed() { return false; } + + protected: + virtual ~MicroTaskRunnable() = default; +}; + +// Store the suppressed mictotasks in another microtask so that operations +// for the microtask queue as a whole keep working. +class SuppressedMicroTasks : public MicroTaskRunnable { + public: + explicit SuppressedMicroTasks(CycleCollectedJSContext* aContext); + + MOZ_CAN_RUN_SCRIPT_BOUNDARY void Run(AutoSlowOperation& aAso) final {} + virtual bool Suppressed(); + + CycleCollectedJSContext* mContext; + uint64_t mSuppressionGeneration; + std::deque<RefPtr<MicroTaskRunnable>> mSuppressedMicroTaskRunnables; +}; + +// Support for JS FinalizationRegistry objects, which allow a JS callback to be +// registered that is called when objects die. +// +// We keep a vector of functions that call back into the JS engine along +// with their associated incumbent globals, one per FinalizationRegistry object +// that has pending cleanup work. These are run in their own task. +class FinalizationRegistryCleanup { + public: + explicit FinalizationRegistryCleanup(CycleCollectedJSContext* aContext); + void Init(); + void Destroy(); + void QueueCallback(JSFunction* aDoCleanup, JSObject* aIncumbentGlobal); + MOZ_CAN_RUN_SCRIPT void DoCleanup(); + + private: + static void QueueCallback(JSFunction* aDoCleanup, JSObject* aIncumbentGlobal, + void* aData); + + class CleanupRunnable; + + struct Callback { + JSFunction* mCallbackFunction; + JSObject* mIncumbentGlobal; + void trace(JSTracer* trc); + }; + + // This object is part of CycleCollectedJSContext, so it's safe to have a raw + // pointer to its containing context here. + CycleCollectedJSContext* mContext; + + using CallbackVector = JS::GCVector<Callback, 0, InfallibleAllocPolicy>; + JS::PersistentRooted<CallbackVector> mCallbacks; +}; + +class CycleCollectedJSContext : dom::PerThreadAtomCache, private JS::JobQueue { + friend class CycleCollectedJSRuntime; + friend class SuppressedMicroTasks; + + protected: + CycleCollectedJSContext(); + virtual ~CycleCollectedJSContext(); + + MOZ_IS_CLASS_INIT + nsresult Initialize(JSRuntime* aParentRuntime, uint32_t aMaxBytes); + + virtual CycleCollectedJSRuntime* CreateRuntime(JSContext* aCx) = 0; + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + + private: + static void PromiseRejectionTrackerCallback( + JSContext* aCx, bool aMutedErrors, JS::Handle<JSObject*> aPromise, + JS::PromiseRejectionHandlingState state, void* aData); + + void AfterProcessMicrotasks(); + + public: + void ProcessStableStateQueue(); + + private: + void CleanupIDBTransactions(uint32_t aRecursionDepth); + + public: + virtual dom::WorkerJSContext* GetAsWorkerJSContext() { return nullptr; } + virtual dom::WorkletJSContext* GetAsWorkletJSContext() { return nullptr; } + + CycleCollectedJSRuntime* Runtime() const { + MOZ_ASSERT(mRuntime); + return mRuntime; + } + + already_AddRefed<dom::Exception> GetPendingException() const; + void SetPendingException(dom::Exception* aException); + + std::deque<RefPtr<MicroTaskRunnable>>& GetMicroTaskQueue(); + std::deque<RefPtr<MicroTaskRunnable>>& GetDebuggerMicroTaskQueue(); + + JSContext* Context() const { + MOZ_ASSERT(mJSContext); + return mJSContext; + } + + JS::RootingContext* RootingCx() const { + MOZ_ASSERT(mJSContext); + return JS::RootingContext::get(mJSContext); + } + + void SetTargetedMicroTaskRecursionDepth(uint32_t aDepth) { + mTargetedMicroTaskRecursionDepth = aDepth; + } + + void UpdateMicroTaskSuppressionGeneration() { ++mSuppressionGeneration; } + + protected: + JSContext* MaybeContext() const { return mJSContext; } + + public: + // nsThread entrypoints + // + // MOZ_CAN_RUN_SCRIPT_BOUNDARY so we don't need to annotate + // nsThread::ProcessNextEvent and all its callers MOZ_CAN_RUN_SCRIPT for now. + // But we really should! + MOZ_CAN_RUN_SCRIPT_BOUNDARY + virtual void BeforeProcessTask(bool aMightBlock); + // MOZ_CAN_RUN_SCRIPT_BOUNDARY so we don't need to annotate + // nsThread::ProcessNextEvent and all its callers MOZ_CAN_RUN_SCRIPT for now. + // But we really should! + MOZ_CAN_RUN_SCRIPT_BOUNDARY + virtual void AfterProcessTask(uint32_t aRecursionDepth); + + // Check whether any eager thresholds have been reached, which would mean + // an idle GC task (minor or major) would be useful. + virtual void MaybePokeGC(); + + uint32_t RecursionDepth() const; + + // Run in stable state (call through nsContentUtils) + void RunInStableState(already_AddRefed<nsIRunnable>&& aRunnable); + + void AddPendingIDBTransaction(already_AddRefed<nsIRunnable>&& aTransaction); + + // Get the CycleCollectedJSContext for a JSContext. + // Returns null only if Initialize() has not completed on or during + // destruction of the CycleCollectedJSContext. + static CycleCollectedJSContext* GetFor(JSContext* aCx); + + // Get the current thread's CycleCollectedJSContext. Returns null if there + // isn't one. + static CycleCollectedJSContext* Get(); + + // Queue an async microtask to the current main or worker thread. + virtual void DispatchToMicroTask( + already_AddRefed<MicroTaskRunnable> aRunnable); + + // Call EnterMicroTask when you're entering JS execution. + // Usually the best way to do this is to use nsAutoMicroTask. + void EnterMicroTask() { ++mMicroTaskLevel; } + + MOZ_CAN_RUN_SCRIPT + void LeaveMicroTask() { + if (--mMicroTaskLevel == 0) { + PerformMicroTaskCheckPoint(); + } + } + + uint32_t MicroTaskLevel() const { return mMicroTaskLevel; } + + void SetMicroTaskLevel(uint32_t aLevel) { mMicroTaskLevel = aLevel; } + + MOZ_CAN_RUN_SCRIPT + bool PerformMicroTaskCheckPoint(bool aForce = false); + + MOZ_CAN_RUN_SCRIPT + void PerformDebuggerMicroTaskCheckpoint(); + + bool IsInStableOrMetaStableState() const { return mDoingStableStates; } + + // Storage for watching rejected promises waiting for some client to + // consume their rejection. + // 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; + nsTArray<nsCOMPtr<nsISupports /* UncaughtRejectionObserver */>> + mUncaughtRejectionObservers; + + virtual bool IsSystemCaller() const = 0; + + // Unused on main thread. Used by AutoJSAPI on Worker and Worklet threads. + virtual void ReportError(JSErrorReport* aReport, + JS::ConstUTF8CharsZ aToStringResult) { + MOZ_ASSERT_UNREACHABLE("Not supported"); + } + + private: + // JS::JobQueue implementation: see js/public/Promise.h. + // SpiderMonkey uses some of these methods to enqueue promise resolution jobs. + // Others protect the debuggee microtask queue from the debugger's + // interruptions; see the comments on JS::AutoDebuggerJobQueueInterruption for + // details. + JSObject* getIncumbentGlobal(JSContext* cx) override; + bool enqueuePromiseJob(JSContext* cx, JS::Handle<JSObject*> promise, + JS::Handle<JSObject*> job, + JS::Handle<JSObject*> allocationSite, + JS::Handle<JSObject*> incumbentGlobal) override; + // MOZ_CAN_RUN_SCRIPT_BOUNDARY for now so we don't have to change SpiderMonkey + // headers. The caller presumably knows this can run script (like everything + // in SpiderMonkey!) and will deal. + MOZ_CAN_RUN_SCRIPT_BOUNDARY + void runJobs(JSContext* cx) override; + bool empty() const override; + class SavedMicroTaskQueue; + js::UniquePtr<SavedJobQueue> saveJobQueue(JSContext*) override; + + private: + CycleCollectedJSRuntime* mRuntime; + + JSContext* mJSContext; + + nsCOMPtr<dom::Exception> mPendingException; + nsThread* mOwningThread; // Manual refcounting to avoid include hell. + + struct PendingIDBTransactionData { + nsCOMPtr<nsIRunnable> mTransaction; + uint32_t mRecursionDepth; + }; + + nsTArray<nsCOMPtr<nsIRunnable>> mStableStateEvents; + nsTArray<PendingIDBTransactionData> mPendingIDBTransactions; + uint32_t mBaseRecursionDepth; + bool mDoingStableStates; + + // If set to none 0, microtasks will be processed only when recursion depth + // is the set value. + uint32_t mTargetedMicroTaskRecursionDepth; + + uint32_t mMicroTaskLevel; + + std::deque<RefPtr<MicroTaskRunnable>> mPendingMicroTaskRunnables; + std::deque<RefPtr<MicroTaskRunnable>> mDebuggerMicroTaskQueue; + RefPtr<SuppressedMicroTasks> mSuppressedMicroTasks; + uint64_t mSuppressionGeneration; + + // How many times the debugger has interrupted execution, possibly creating + // microtask checkpoints in places that they would not normally occur. + uint32_t mDebuggerRecursionDepth; + + uint32_t mMicroTaskRecursionDepth; + + // This implements about-to-be-notified rejected promises list in the spec. + // https://html.spec.whatwg.org/multipage/webappapis.html#about-to-be-notified-rejected-promises-list + typedef nsTArray<RefPtr<dom::Promise>> PromiseArray; + PromiseArray mAboutToBeNotifiedRejectedPromises; + + // This is for the "outstanding rejected promises weak set" in the spec, + // https://html.spec.whatwg.org/multipage/webappapis.html#outstanding-rejected-promises-weak-set + // We use different data structure and opposite logic here to achieve the same + // effect. Basically this is used for tracking the rejected promise that does + // NOT need firing a rejectionhandled event. We will check the table to see if + // firing rejectionhandled event is required when a rejected promise is being + // handled. + // + // The rejected promise will be stored in the table if + // - it is unhandled, and + // - The unhandledrejection is not yet fired. + // + // And be removed when + // - it is handled, or + // - A unhandledrejection is fired and it isn't being handled in event + // handler. + typedef nsRefPtrHashtable<nsUint64HashKey, dom::Promise> PromiseHashtable; + PromiseHashtable mPendingUnhandledRejections; + + class NotifyUnhandledRejections final : public CancelableRunnable { + public: + explicit NotifyUnhandledRejections(PromiseArray&& aPromises) + : CancelableRunnable("NotifyUnhandledRejections"), + mUnhandledRejections(std::move(aPromises)) {} + + NS_IMETHOD Run() final; + + nsresult Cancel() final; + + private: + PromiseArray mUnhandledRejections; + }; + + FinalizationRegistryCleanup mFinalizationRegistryCleanup; +}; + +class MOZ_STACK_CLASS nsAutoMicroTask { + public: + nsAutoMicroTask() { + CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get(); + if (ccjs) { + ccjs->EnterMicroTask(); + } + } + MOZ_CAN_RUN_SCRIPT ~nsAutoMicroTask() { + CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get(); + if (ccjs) { + ccjs->LeaveMicroTask(); + } + } +}; + +} // namespace mozilla + +#endif // mozilla_CycleCollectedJSContext_h diff --git a/xpcom/base/CycleCollectedJSRuntime.cpp b/xpcom/base/CycleCollectedJSRuntime.cpp new file mode 100644 index 0000000000..03af9e3074 --- /dev/null +++ b/xpcom/base/CycleCollectedJSRuntime.cpp @@ -0,0 +1,2027 @@ +/* -*- 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 +// TraceNativeGrayRootsInCollectingZones 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/CycleCollectedJSRuntime.h" + +#include <algorithm> +#include <utility> + +#include "js/Debug.h" +#include "js/RealmOptions.h" +#include "js/friend/DumpFunctions.h" // js::DumpHeap +#include "js/GCAPI.h" +#include "js/HeapAPI.h" +#include "js/Object.h" // JS::GetClass, JS::GetCompartment, JS::GetPrivate +#include "js/PropertyAndElement.h" // JS_DefineProperty +#include "js/Warnings.h" // JS::SetWarningReporter +#include "js/ShadowRealmCallbacks.h" +#include "js/SliceBudget.h" +#include "jsfriendapi.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/AutoRestore.h" +#include "mozilla/CycleCollectedJSContext.h" +#include "mozilla/DebuggerOnGCRunnable.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/PerfStats.h" +#include "mozilla/ProfilerLabels.h" +#include "mozilla/ProfilerMarkers.h" +#include "mozilla/Sprintf.h" +#include "mozilla/StaticPrefs_javascript.h" +#include "mozilla/Telemetry.h" +#include "mozilla/Unused.h" +#include "mozilla/dom/AutoEntryScript.h" +#include "mozilla/dom/DOMJSClass.h" +#include "mozilla/dom/JSExecutionManager.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/PromiseBinding.h" +#include "mozilla/dom/PromiseDebugging.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/dom/ShadowRealmGlobalScope.h" +#include "mozilla/dom/RegisterShadowRealmBindings.h" +#include "nsContentUtils.h" +#include "nsCycleCollectionNoteRootCallback.h" +#include "nsCycleCollectionParticipant.h" +#include "nsCycleCollector.h" +#include "nsDOMJSUtils.h" +#include "nsExceptionHandler.h" +#include "nsJSUtils.h" +#include "nsStringBuffer.h" +#include "nsWrapperCache.h" +#include "prenv.h" + +#if defined(XP_MACOSX) +# include "nsMacUtilsImpl.h" +#endif + +#include "nsThread.h" +#include "nsThreadUtils.h" +#include "xpcpublic.h" + +#ifdef NIGHTLY_BUILD +// For performance reasons, we make the JS Dev Error Interceptor a Nightly-only +// feature. +# define MOZ_JS_DEV_ERROR_INTERCEPTOR = 1 +#endif // NIGHTLY_BUILD + +using namespace mozilla; +using namespace mozilla::dom; + +namespace mozilla { + +struct DeferredFinalizeFunctionHolder { + DeferredFinalizeFunction run; + void* data; +}; + +class IncrementalFinalizeRunnable : public DiscardableRunnable { + typedef AutoTArray<DeferredFinalizeFunctionHolder, 16> DeferredFinalizeArray; + typedef CycleCollectedJSRuntime::DeferredFinalizerTable + DeferredFinalizerTable; + + CycleCollectedJSRuntime* mRuntime; + DeferredFinalizeArray mDeferredFinalizeFunctions; + uint32_t mFinalizeFunctionToRun; + bool mReleasing; + + static const PRTime SliceMillis = 5; /* ms */ + + public: + IncrementalFinalizeRunnable(CycleCollectedJSRuntime* aRt, + DeferredFinalizerTable& aFinalizerTable); + virtual ~IncrementalFinalizeRunnable(); + + void ReleaseNow(bool aLimited); + + NS_DECL_NSIRUNNABLE +}; + +} // namespace mozilla + +struct NoteWeakMapChildrenTracer : public JS::CallbackTracer { + NoteWeakMapChildrenTracer(JSRuntime* aRt, + nsCycleCollectionNoteRootCallback& aCb) + : JS::CallbackTracer(aRt, JS::TracerKind::Callback), + mCb(aCb), + mTracedAny(false), + mMap(nullptr), + mKey(nullptr), + mKeyDelegate(nullptr) {} + void onChild(JS::GCCellPtr aThing, const char* name) override; + nsCycleCollectionNoteRootCallback& mCb; + bool mTracedAny; + JSObject* mMap; + JS::GCCellPtr mKey; + JSObject* mKeyDelegate; +}; + +void NoteWeakMapChildrenTracer::onChild(JS::GCCellPtr aThing, + const char* name) { + if (aThing.is<JSString>()) { + return; + } + + if (!JS::GCThingIsMarkedGrayInCC(aThing) && !mCb.WantAllTraces()) { + return; + } + + if (JS::IsCCTraceKind(aThing.kind())) { + mCb.NoteWeakMapping(mMap, mKey, mKeyDelegate, aThing); + mTracedAny = true; + } else { + JS::TraceChildren(this, aThing); + } +} + +struct NoteWeakMapsTracer : public js::WeakMapTracer { + NoteWeakMapsTracer(JSRuntime* aRt, nsCycleCollectionNoteRootCallback& aCccb) + : js::WeakMapTracer(aRt), mCb(aCccb), mChildTracer(aRt, 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::GCThingIsMarkedGrayInCC(aKey)) && + MOZ_LIKELY(!mCb.WantAllTraces())) { + if (!aValue || !JS::GCThingIsMarkedGrayInCC(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(JS::IsCCTraceKind(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 (!JS::IsCCTraceKind(aKey.kind())) { + aKey = nullptr; + } + + JSObject* kdelegate = nullptr; + if (aKey.is<JSObject>()) { + kdelegate = js::UncheckedUnwrapWithoutExpose(&aKey.as<JSObject>()); + } + + if (JS::IsCCTraceKind(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::GCThingIsMarkedGrayInCC(aKey) && + kdelegate) { + mCb.NoteWeakMapping(aMap, aKey, kdelegate, nullptr); + } + } +} + +// Report whether the key or value of a weak mapping entry are gray but need to +// be marked black. +static void ShouldWeakMappingEntryBeBlack(JSObject* aMap, JS::GCCellPtr aKey, + JS::GCCellPtr aValue, + bool* aKeyShouldBeBlack, + bool* aValueShouldBeBlack) { + *aKeyShouldBeBlack = false; + *aValueShouldBeBlack = false; + + // If nothing that could be held alive by this entry is marked gray, return. + bool keyMightNeedMarking = aKey && JS::GCThingIsMarkedGrayInCC(aKey); + bool valueMightNeedMarking = aValue && JS::GCThingIsMarkedGrayInCC(aValue) && + aValue.kind() != JS::TraceKind::String; + if (!keyMightNeedMarking && !valueMightNeedMarking) { + return; + } + + if (!JS::IsCCTraceKind(aKey.kind())) { + aKey = nullptr; + } + + if (keyMightNeedMarking && aKey.is<JSObject>()) { + JSObject* kdelegate = + js::UncheckedUnwrapWithoutExpose(&aKey.as<JSObject>()); + if (kdelegate && !JS::ObjectIsMarkedGray(kdelegate) && + (!aMap || !JS::ObjectIsMarkedGray(aMap))) { + *aKeyShouldBeBlack = true; + } + } + + if (aValue && JS::GCThingIsMarkedGrayInCC(aValue) && + (!aKey || !JS::GCThingIsMarkedGrayInCC(aKey)) && + (!aMap || !JS::ObjectIsMarkedGray(aMap)) && + aValue.kind() != JS::TraceKind::Shape) { + *aValueShouldBeBlack = true; + } +} + +struct FixWeakMappingGrayBitsTracer : public js::WeakMapTracer { + explicit FixWeakMappingGrayBitsTracer(JSRuntime* aRt) + : js::WeakMapTracer(aRt) {} + + void FixAll() { + do { + mAnyMarked = false; + js::TraceWeakMaps(this); + } while (mAnyMarked); + } + + void trace(JSObject* aMap, JS::GCCellPtr aKey, + JS::GCCellPtr aValue) override { + bool keyShouldBeBlack; + bool valueShouldBeBlack; + ShouldWeakMappingEntryBeBlack(aMap, aKey, aValue, &keyShouldBeBlack, + &valueShouldBeBlack); + if (keyShouldBeBlack && JS::UnmarkGrayGCThingRecursively(aKey)) { + mAnyMarked = true; + } + + if (valueShouldBeBlack && JS::UnmarkGrayGCThingRecursively(aValue)) { + mAnyMarked = true; + } + } + + MOZ_INIT_OUTSIDE_CTOR bool mAnyMarked; +}; + +#ifdef DEBUG +// Check whether weak maps are marked correctly according to the logic above. +struct CheckWeakMappingGrayBitsTracer : public js::WeakMapTracer { + explicit CheckWeakMappingGrayBitsTracer(JSRuntime* aRt) + : js::WeakMapTracer(aRt), mFailed(false) {} + + static bool Check(JSRuntime* aRt) { + CheckWeakMappingGrayBitsTracer tracer(aRt); + js::TraceWeakMaps(&tracer); + return !tracer.mFailed; + } + + void trace(JSObject* aMap, JS::GCCellPtr aKey, + JS::GCCellPtr aValue) override { + bool keyShouldBeBlack; + bool valueShouldBeBlack; + ShouldWeakMappingEntryBeBlack(aMap, aKey, aValue, &keyShouldBeBlack, + &valueShouldBeBlack); + + if (keyShouldBeBlack) { + fprintf(stderr, "Weak mapping key %p of map %p should be black\n", + aKey.asCell(), aMap); + mFailed = true; + } + + if (valueShouldBeBlack) { + fprintf(stderr, "Weak mapping value %p of map %p should be black\n", + aValue.asCell(), aMap); + mFailed = true; + } + } + + bool mFailed; +}; +#endif // DEBUG + +static void CheckParticipatesInCycleCollection(JS::GCCellPtr aThing, + const char* aName, + void* aClosure) { + bool* cycleCollectionEnabled = static_cast<bool*>(aClosure); + + if (*cycleCollectionEnabled) { + return; + } + + if (JS::IsCCTraceKind(aThing.kind()) && JS::GCThingIsMarkedGrayInCC(aThing)) { + *cycleCollectionEnabled = true; + } +} + +NS_IMETHODIMP +JSGCThingParticipant::TraverseNative(void* aPtr, + nsCycleCollectionTraversalCallback& aCb) { + auto runtime = reinterpret_cast<CycleCollectedJSRuntime*>( + reinterpret_cast<char*>(this) - + offsetof(CycleCollectedJSRuntime, mGCThingCycleCollectorGlobal)); + + JS::GCCellPtr cellPtr(aPtr, JS::GCThingTraceKind(aPtr)); + runtime->TraverseGCThing(CycleCollectedJSRuntime::TRAVERSE_FULL, cellPtr, + aCb); + return NS_OK; +} + +// NB: This is only used to initialize the participant in +// CycleCollectedJSRuntime. It should never be used directly. +static JSGCThingParticipant sGCThingCycleCollectorGlobal; + +NS_IMETHODIMP +JSZoneParticipant::TraverseNative(void* aPtr, + nsCycleCollectionTraversalCallback& aCb) { + auto runtime = reinterpret_cast<CycleCollectedJSRuntime*>( + reinterpret_cast<char*>(this) - + offsetof(CycleCollectedJSRuntime, 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(JSRuntime* aRt, nsCycleCollectionTraversalCallback& aCb) + : JS::CallbackTracer(aRt, JS::TracerKind::Callback, + JS::TraceOptions(JS::WeakMapTraceAction::Skip, + JS::WeakEdgeTraceAction::Trace)), + mCb(aCb) {} + void onChild(JS::GCCellPtr aThing, const char* name) override; + nsCycleCollectionTraversalCallback& mCb; +}; + +void TraversalTracer::onChild(JS::GCCellPtr aThing, const char* name) { + // Checking strings and symbols for being gray is rather slow, and we don't + // need either of them for the cycle collector. + if (aThing.is<JSString>() || aThing.is<JS::Symbol>()) { + return; + } + + // Don't traverse non-gray objects, unless we want all traces. + if (!JS::GCThingIsMarkedGrayInCC(aThing) && !mCb.WantAllTraces()) { + return; + } + + /* + * This function needs to be careful to avoid stack overflow. Normally, when + * IsCCTraceKind 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-IsCCTraceKind GC things. Places where this can + * occur use special APIs to handle such chains iteratively. + */ + if (JS::IsCCTraceKind(aThing.kind())) { + if (MOZ_UNLIKELY(mCb.WantDebugInfo())) { + char buffer[200]; + context().getEdgeName(name, buffer, sizeof(buffer)); + mCb.NoteNextEdgeName(buffer); + } + mCb.NoteJSChild(aThing); + return; + } + + // Allow re-use of this tracer inside trace callback. + JS::AutoClearTracingContext actc(this); + + 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 { + JS::TraceChildren(this, 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 +// CycleCollectedJSRuntime. It should never be used directly. +static const JSZoneParticipant sJSZoneCycleCollectorGlobal; + +static void JSObjectsTenuredCb(JSContext* aContext, void* aData) { + static_cast<CycleCollectedJSRuntime*>(aData)->JSObjectsTenured(); +} + +static void MozCrashWarningReporter(JSContext*, JSErrorReport*) { + MOZ_CRASH("Why is someone touching JSAPI without an AutoJSAPI?"); +} + +JSHolderMap::Entry::Entry() : Entry(nullptr, nullptr, nullptr) {} + +JSHolderMap::Entry::Entry(void* aHolder, nsScriptObjectTracer* aTracer, + JS::Zone* aZone) + : mHolder(aHolder), + mTracer(aTracer) +#ifdef DEBUG + , + mZone(aZone) +#endif +{ +} + +void JSHolderMap::EntryVectorIter::Settle() { + if (Done()) { + return; + } + + Entry* entry = &mIter.Get(); + + // If the entry has been cleared, remove it and shrink the vector. + if (!entry->mHolder && !mHolderMap.RemoveEntry(mVector, entry)) { + // We removed the last entry, so reset the iterator to an empty one. + mIter = EntryVector().Iter(); + MOZ_ASSERT(Done()); + } +} + +JSHolderMap::Iter::Iter(JSHolderMap& aMap, WhichHolders aWhich) + : mHolderMap(aMap), mIter(aMap, aMap.mAnyZoneJSHolders) { + MOZ_RELEASE_ASSERT(!mHolderMap.mHasIterator); + mHolderMap.mHasIterator = true; + + // Populate vector of zones to iterate after the any-zone holders. + for (auto i = aMap.mPerZoneJSHolders.iter(); !i.done(); i.next()) { + JS::Zone* zone = i.get().key(); + if (aWhich == AllHolders || JS::NeedGrayRootsForZone(i.get().key())) { + MOZ_ALWAYS_TRUE(mZones.append(zone)); + } + } + + Settle(); +} + +void JSHolderMap::Iter::Settle() { + while (mIter.Done()) { + if (mZone && mIter.Vector().IsEmpty()) { + mHolderMap.mPerZoneJSHolders.remove(mZone); + } + + mZone = nullptr; + if (mZones.empty()) { + break; + } + + mZone = mZones.popCopy(); + EntryVector& vector = *mHolderMap.mPerZoneJSHolders.lookup(mZone)->value(); + new (&mIter) EntryVectorIter(mHolderMap, vector); + } +} + +void JSHolderMap::Iter::UpdateForRemovals() { + mIter.Settle(); + Settle(); +} + +JSHolderMap::JSHolderMap() : mJSHolderMap(256) {} + +bool JSHolderMap::RemoveEntry(EntryVector& aJSHolders, Entry* aEntry) { + MOZ_ASSERT(aEntry); + MOZ_ASSERT(!aEntry->mHolder); + + // Remove all dead entries from the end of the vector. + while (!aJSHolders.GetLast().mHolder && &aJSHolders.GetLast() != aEntry) { + aJSHolders.PopLast(); + } + + // Swap the element we want to remove with the last one and update the hash + // table. + Entry* lastEntry = &aJSHolders.GetLast(); + if (aEntry != lastEntry) { + MOZ_ASSERT(lastEntry->mHolder); + *aEntry = *lastEntry; + MOZ_ASSERT(mJSHolderMap.has(aEntry->mHolder)); + MOZ_ALWAYS_TRUE(mJSHolderMap.put(aEntry->mHolder, aEntry)); + } + + aJSHolders.PopLast(); + + // Return whether aEntry is still in the vector. + return aEntry != lastEntry; +} + +bool JSHolderMap::Has(void* aHolder) const { return mJSHolderMap.has(aHolder); } + +nsScriptObjectTracer* JSHolderMap::Get(void* aHolder) const { + auto ptr = mJSHolderMap.lookup(aHolder); + if (!ptr) { + return nullptr; + } + + Entry* entry = ptr->value(); + MOZ_ASSERT(entry->mHolder == aHolder); + return entry->mTracer; +} + +nsScriptObjectTracer* JSHolderMap::Extract(void* aHolder) { + MOZ_ASSERT(aHolder); + + auto ptr = mJSHolderMap.lookup(aHolder); + if (!ptr) { + return nullptr; + } + + Entry* entry = ptr->value(); + MOZ_ASSERT(entry->mHolder == aHolder); + nsScriptObjectTracer* tracer = entry->mTracer; + + // Clear the entry's contents. It will be removed the next time iteration + // visits this entry. + *entry = Entry(); + + mJSHolderMap.remove(ptr); + + return tracer; +} + +void JSHolderMap::Put(void* aHolder, nsScriptObjectTracer* aTracer, + JS::Zone* aZone) { + MOZ_ASSERT(aHolder); + MOZ_ASSERT(aTracer); + + // Don't associate multi-zone holders with a zone, even if one is supplied. + if (!aTracer->IsSingleZoneJSHolder()) { + aZone = nullptr; + } + + auto ptr = mJSHolderMap.lookupForAdd(aHolder); + if (ptr) { + Entry* entry = ptr->value(); +#ifdef DEBUG + MOZ_ASSERT(entry->mHolder == aHolder); + MOZ_ASSERT(entry->mTracer == aTracer, + "Don't call HoldJSObjects in superclass ctors"); + if (aZone) { + if (entry->mZone) { + MOZ_ASSERT(entry->mZone == aZone); + } else { + entry->mZone = aZone; + } + } +#endif + entry->mTracer = aTracer; + return; + } + + EntryVector* vector = &mAnyZoneJSHolders; + if (aZone) { + auto ptr = mPerZoneJSHolders.lookupForAdd(aZone); + if (!ptr) { + MOZ_ALWAYS_TRUE( + mPerZoneJSHolders.add(ptr, aZone, MakeUnique<EntryVector>())); + } + vector = ptr->value().get(); + } + + vector->InfallibleAppend(Entry{aHolder, aTracer, aZone}); + MOZ_ALWAYS_TRUE(mJSHolderMap.add(ptr, aHolder, &vector->GetLast())); +} + +size_t JSHolderMap::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { + size_t n = 0; + + // We're deliberately not measuring anything hanging off the entries in + // mJSHolders. + n += mJSHolderMap.shallowSizeOfExcludingThis(aMallocSizeOf); + n += mAnyZoneJSHolders.SizeOfExcludingThis(aMallocSizeOf); + n += mPerZoneJSHolders.shallowSizeOfExcludingThis(aMallocSizeOf); + for (auto i = mPerZoneJSHolders.iter(); !i.done(); i.next()) { + n += i.get().value()->SizeOfExcludingThis(aMallocSizeOf); + } + + return n; +} + +static bool InitializeShadowRealm(JSContext* aCx, + JS::Handle<JSObject*> aGlobal) { + MOZ_ASSERT(StaticPrefs::javascript_options_experimental_shadow_realms()); + + JSAutoRealm ar(aCx, aGlobal); + return dom::RegisterShadowRealmBindings(aCx, aGlobal); +} + +CycleCollectedJSRuntime::CycleCollectedJSRuntime(JSContext* aCx) + : mContext(nullptr), + mGCThingCycleCollectorGlobal(sGCThingCycleCollectorGlobal), + mJSZoneCycleCollectorGlobal(sJSZoneCycleCollectorGlobal), + mJSRuntime(JS_GetRuntime(aCx)), + mHasPendingIdleGCTask(false), + mPrevGCSliceCallback(nullptr), + mOutOfMemoryState(OOMState::OK), + mLargeAllocationFailureState(OOMState::OK) +#ifdef DEBUG + , + mShutdownCalled(false) +#endif +{ + MOZ_COUNT_CTOR(CycleCollectedJSRuntime); + MOZ_ASSERT(aCx); + MOZ_ASSERT(mJSRuntime); + +#if defined(XP_MACOSX) + if (!XRE_IsParentProcess()) { + nsMacUtilsImpl::EnableTCSMIfAvailable(); + } +#endif + + if (!JS_AddExtraGCRootsTracer(aCx, TraceBlackJS, this)) { + MOZ_CRASH("JS_AddExtraGCRootsTracer failed"); + } + JS_SetGrayGCRootsTracer(aCx, TraceGrayJS, this); + JS_SetGCCallback(aCx, GCCallback, this); + mPrevGCSliceCallback = JS::SetGCSliceCallback(aCx, 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. + JS::AddGCNurseryCollectionCallback(aCx, GCNurseryCollectionCallback, + nullptr); + } + + JS_SetObjectsTenuredCallback(aCx, JSObjectsTenuredCb, this); + JS::SetOutOfMemoryCallback(aCx, OutOfMemoryCallback, this); + JS::SetWaitCallback(mJSRuntime, BeforeWaitCallback, AfterWaitCallback, + sizeof(dom::AutoYieldJSThreadExecution)); + JS::SetWarningReporter(aCx, MozCrashWarningReporter); + JS::SetShadowRealmInitializeGlobalCallback(aCx, InitializeShadowRealm); + JS::SetShadowRealmGlobalCreationCallback(aCx, dom::NewShadowRealmGlobal); + + js::AutoEnterOOMUnsafeRegion::setAnnotateOOMAllocationSizeCallback( + CrashReporter::AnnotateOOMAllocationSize); + + static js::DOMCallbacks DOMcallbacks = {InstanceClassHasProtoAtDepth}; + SetDOMCallbacks(aCx, &DOMcallbacks); + js::SetScriptEnvironmentPreparer(aCx, &mEnvironmentPreparer); + + JS::dbg::SetDebuggerMallocSizeOf(aCx, moz_malloc_size_of); + +#ifdef MOZ_JS_DEV_ERROR_INTERCEPTOR + JS_SetErrorInterceptorCallback(mJSRuntime, &mErrorInterceptor); +#endif // MOZ_JS_DEV_ERROR_INTERCEPTOR + + JS_SetDestroyZoneCallback(aCx, OnZoneDestroyed); +} + +#ifdef NS_BUILD_REFCNT_LOGGING +class JSLeakTracer : public JS::CallbackTracer { + public: + explicit JSLeakTracer(JSRuntime* aRuntime) + : JS::CallbackTracer(aRuntime, JS::TracerKind::Callback, + JS::WeakMapTraceAction::TraceKeysAndValues) {} + + private: + void onChild(JS::GCCellPtr thing, const char* name) override { + const char* kindName = JS::GCTraceKindToAscii(thing.kind()); + size_t size = JS::GCTraceKindSize(thing.kind()); + MOZ_LOG_CTOR(thing.asCell(), kindName, size); + } +}; +#endif + +void CycleCollectedJSRuntime::Shutdown(JSContext* aCx) { +#ifdef MOZ_JS_DEV_ERROR_INTERCEPTOR + mErrorInterceptor.Shutdown(mJSRuntime); +#endif // MOZ_JS_DEV_ERROR_INTERCEPTOR + + // There should not be any roots left to trace at this point. Ensure any that + // remain are flagged as leaks. +#ifdef NS_BUILD_REFCNT_LOGGING + JSLeakTracer tracer(Runtime()); + TraceNativeBlackRoots(&tracer); + TraceAllNativeGrayRoots(&tracer); +#endif + +#ifdef DEBUG + mShutdownCalled = true; +#endif + + JS_SetDestroyZoneCallback(aCx, nullptr); + + if (NS_IsMainThread()) { + JS::RemoveGCNurseryCollectionCallback(aCx, GCNurseryCollectionCallback, + nullptr); + } +} + +CycleCollectedJSRuntime::~CycleCollectedJSRuntime() { + MOZ_COUNT_DTOR(CycleCollectedJSRuntime); + MOZ_ASSERT(!mDeferredFinalizerTable.Count()); + MOZ_ASSERT(!mFinalizeRunnable); + MOZ_ASSERT(mShutdownCalled); +} + +void CycleCollectedJSRuntime::SetContext(CycleCollectedJSContext* aContext) { + MOZ_ASSERT(!mContext || !aContext, "Don't replace the context!"); + mContext = aContext; +} + +size_t CycleCollectedJSRuntime::SizeOfExcludingThis( + MallocSizeOf aMallocSizeOf) const { + return mJSHolders.SizeOfExcludingThis(aMallocSizeOf); +} + +void CycleCollectedJSRuntime::UnmarkSkippableJSHolders() { + for (JSHolderMap::Iter entry(mJSHolders); !entry.Done(); entry.Next()) { + entry->mTracer->CanSkip(entry->mHolder, true); + } +} + +void CycleCollectedJSRuntime::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::GetCompartment(obj); + const JSClass* clasp = JS::GetClass(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_GetMaybePartialFunctionDisplayId(fun); + if (str) { + JSLinearString* linear = JS_ASSERT_STRING_IS_LINEAR(str); + nsAutoString chars; + AssignJSLinearString(chars, linear); + 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, "%s", JS::GCTraceKindToAscii(aThing.kind())); + } + + // Disable printing global for objects while we figure out ObjShrink fallout. + aCb.DescribeGCedNode(aIsMarked, name, compartmentAddress); +} + +void CycleCollectedJSRuntime::NoteGCThingJSChildren( + JS::GCCellPtr aThing, nsCycleCollectionTraversalCallback& aCb) const { + TraversalTracer trc(mJSRuntime, aCb); + JS::TraceChildren(&trc, aThing); +} + +void CycleCollectedJSRuntime::NoteGCThingXPCOMChildren( + const JSClass* aClasp, JSObject* aObj, + nsCycleCollectionTraversalCallback& aCb) const { + MOZ_ASSERT(aClasp); + MOZ_ASSERT(aClasp == JS::GetClass(aObj)); + + JS::Rooted<JSObject*> obj(RootingCx(), aObj); + + if (NoteCustomGCThingXPCOMChildren(aClasp, obj, aCb)) { + // Nothing else to do! + return; + } + + // XXX This test does seem fragile, we should probably allowlist classes + // that do hold a strong reference, but that might not be possible. + if (aClasp->slot0IsISupports()) { + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "JS::GetObjectISupports(obj)"); + aCb.NoteXPCOMChild(JS::GetObjectISupports<nsISupports>(obj)); + return; + } + + const DOMJSClass* domClass = GetDOMClass(aClasp); + 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>(obj)); + } else if (domClass->mParticipant) { + aCb.NoteNativeChild(UnwrapPossiblyNotInitializedDOMObject<void>(obj), + domClass->mParticipant); + } + return; + } + + if (IsRemoteObjectProxy(obj)) { + auto handler = + static_cast<const RemoteObjectProxyBase*>(js::GetProxyHandler(obj)); + return handler->NoteChildren(obj, aCb); + } + + JS::Value value = js::MaybeGetScriptPrivate(obj); + if (!value.isUndefined()) { + aCb.NoteXPCOMChild(static_cast<nsISupports*>(value.toPrivate())); + } +} + +void CycleCollectedJSRuntime::TraverseGCThing( + TraverseSelect aTs, JS::GCCellPtr aThing, + nsCycleCollectionTraversalCallback& aCb) { + bool isMarkedGray = JS::GCThingIsMarkedGrayInCC(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::GetClass(obj), obj, aCb); + } +} + +struct TraverseObjectShimClosure { + nsCycleCollectionTraversalCallback& cb; + CycleCollectedJSRuntime* self; +}; + +void CycleCollectedJSRuntime::TraverseZone( + JS::Zone* aZone, nsCycleCollectionTraversalCallback& aCb) { + /* + * 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(mJSRuntime, aCb); + js::TraceGrayWrapperTargets(&trc, aZone); + + /* + * 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 CycleCollectedJSRuntime::TraverseObjectShim( + void* aData, JS::GCCellPtr aThing, const JS::AutoRequireNoGC& nogc) { + TraverseObjectShimClosure* closure = + static_cast<TraverseObjectShimClosure*>(aData); + + MOZ_ASSERT(aThing.is<JSObject>()); + closure->self->TraverseGCThing(CycleCollectedJSRuntime::TRAVERSE_CPP, aThing, + closure->cb); +} + +void CycleCollectedJSRuntime::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 (JSHolderMap::Iter entry(mJSHolders); !entry.Done(); entry.Next()) { + void* holder = entry->mHolder; + nsScriptObjectTracer* tracer = entry->mTracer; + + 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 CycleCollectedJSRuntime::TraceBlackJS(JSTracer* aTracer, void* aData) { + CycleCollectedJSRuntime* self = static_cast<CycleCollectedJSRuntime*>(aData); + + self->TraceNativeBlackRoots(aTracer); +} + +/* static */ +bool CycleCollectedJSRuntime::TraceGrayJS(JSTracer* aTracer, + js::SliceBudget& budget, + void* aData) { + CycleCollectedJSRuntime* self = static_cast<CycleCollectedJSRuntime*>(aData); + + // Mark these roots as gray so the CC can walk them later. + + JSHolderMap::WhichHolders which = JSHolderMap::AllHolders; + + // Only trace holders in collecting zones when marking, except if we are + // collecting the atoms zone since any holder may point into that zone. + if (aTracer->isMarkingTracer() && + !JS::AtomsZoneIsCollecting(self->Runtime())) { + which = JSHolderMap::HoldersRequiredForGrayMarking; + } + + return self->TraceNativeGrayRoots(aTracer, which, budget); +} + +/* static */ +void CycleCollectedJSRuntime::GCCallback(JSContext* aContext, + JSGCStatus aStatus, + JS::GCReason aReason, void* aData) { + CycleCollectedJSRuntime* self = static_cast<CycleCollectedJSRuntime*>(aData); + + MOZ_ASSERT(CycleCollectedJSContext::Get()->Context() == aContext); + MOZ_ASSERT(CycleCollectedJSContext::Get()->Runtime() == self); + + self->OnGC(aContext, aStatus, aReason); +} + +struct GCMajorMarker : public BaseMarkerType<GCMajorMarker> { + static constexpr const char* Name = "GCMajor"; + static constexpr const char* Description = + "Summary data for an entire major GC, encompassing a set of " + "incremental slices. The main thread is not blocked for the " + "entire major GC interval, only for the individual slices."; + + using MS = MarkerSchema; + static constexpr MS::PayloadField PayloadFields[] = { + {"timings", MS::InputType::String, "GC timings"}}; + + static constexpr MS::Location Locations[] = {MS::Location::MarkerChart, + MS::Location::MarkerTable, + MS::Location::TimelineMemory}; + static constexpr MS::ETWMarkerGroup Group = MS::ETWMarkerGroup::Memory; + + static void StreamJSONMarkerData( + mozilla::baseprofiler::SpliceableJSONWriter& aWriter, + const mozilla::ProfilerString8View& aTimingJSON) { + if (aTimingJSON.Length() != 0) { + aWriter.SplicedJSONProperty("timings", aTimingJSON); + } else { + aWriter.NullProperty("timings"); + } + } +}; + +/* static */ +void CycleCollectedJSRuntime::GCSliceCallback(JSContext* aContext, + JS::GCProgress aProgress, + const JS::GCDescription& aDesc) { + CycleCollectedJSRuntime* self = CycleCollectedJSRuntime::Get(); + MOZ_ASSERT(CycleCollectedJSContext::Get()->Context() == aContext); + + if (profiler_thread_is_being_profiled_for_markers()) { + if (aProgress == JS::GC_CYCLE_END) { + profiler_add_marker("GCMajor", baseprofiler::category::GCCC, + MarkerTiming::Interval(aDesc.startTime(aContext), + aDesc.endTime(aContext)), + GCMajorMarker{}, + ProfilerString8View::WrapNullTerminatedString( + aDesc.formatJSONProfiler(aContext).get())); + } else if (aProgress == JS::GC_SLICE_END) { + struct GCSliceMarker { + static constexpr mozilla::Span<const char> MarkerTypeName() { + return mozilla::MakeStringSpan("GCSlice"); + } + static void StreamJSONMarkerData( + mozilla::baseprofiler::SpliceableJSONWriter& aWriter, + const mozilla::ProfilerString8View& aTimingJSON) { + if (aTimingJSON.Length() != 0) { + aWriter.SplicedJSONProperty("timings", aTimingJSON); + } else { + aWriter.NullProperty("timings"); + } + } + static mozilla::MarkerSchema MarkerTypeDisplay() { + using MS = mozilla::MarkerSchema; + MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable, + MS::Location::TimelineMemory}; + schema.AddStaticLabelValue( + "Description", + "One slice of an incremental garbage collection (GC). The main " + "thread is blocked during this time."); + // No display instructions here, there is special handling in the + // front-end. + return schema; + } + }; + + profiler_add_marker("GCSlice", baseprofiler::category::GCCC, + MarkerTiming::Interval(aDesc.lastSliceStart(aContext), + aDesc.lastSliceEnd(aContext)), + GCSliceMarker{}, + ProfilerString8View::WrapNullTerminatedString( + aDesc.sliceToJSONProfiler(aContext).get())); + } + } + + if (aProgress == JS::GC_CYCLE_END && + JS::dbg::FireOnGarbageCollectionHookRequired(aContext)) { + JS::GCReason 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); + } +} + +/* static */ +void CycleCollectedJSRuntime::GCNurseryCollectionCallback( + JSContext* aContext, JS::GCNurseryProgress aProgress, JS::GCReason aReason, + void* data) { + CycleCollectedJSRuntime* self = CycleCollectedJSRuntime::Get(); + MOZ_ASSERT(CycleCollectedJSContext::Get()->Context() == aContext); + MOZ_ASSERT(NS_IsMainThread()); + + TimeStamp now = TimeStamp::Now(); + if (aProgress == JS::GCNurseryProgress::GC_NURSERY_COLLECTION_START) { + self->mLatestNurseryCollectionStart = now; + } else if (aProgress == JS::GCNurseryProgress::GC_NURSERY_COLLECTION_END) { + PerfStats::RecordMeasurement(PerfStats::Metric::MinorGC, + now - self->mLatestNurseryCollectionStart); + } + + if (aProgress == JS::GCNurseryProgress::GC_NURSERY_COLLECTION_END && + profiler_thread_is_being_profiled_for_markers()) { + struct GCMinorMarker { + static constexpr mozilla::Span<const char> MarkerTypeName() { + return mozilla::MakeStringSpan("GCMinor"); + } + static void StreamJSONMarkerData( + mozilla::baseprofiler::SpliceableJSONWriter& aWriter, + const mozilla::ProfilerString8View& aTimingJSON) { + if (aTimingJSON.Length() != 0) { + aWriter.SplicedJSONProperty("nursery", aTimingJSON); + } else { + aWriter.NullProperty("nursery"); + } + } + static mozilla::MarkerSchema MarkerTypeDisplay() { + using MS = mozilla::MarkerSchema; + MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable, + MS::Location::TimelineMemory}; + schema.AddStaticLabelValue( + "Description", + "A minor GC (aka nursery collection) to clear out the buffer used " + "for recent allocations and move surviving data to the tenured " + "(long-lived) heap."); + // No display instructions here, there is special handling in the + // front-end. + return schema; + } + }; + + profiler_add_marker( + "GCMinor", baseprofiler::category::GCCC, + MarkerTiming::Interval(self->mLatestNurseryCollectionStart, now), + GCMinorMarker{}, + ProfilerString8View::WrapNullTerminatedString( + JS::MinorGcToJSON(aContext).get())); + } +} + +/* static */ +void CycleCollectedJSRuntime::OutOfMemoryCallback(JSContext* aContext, + void* aData) { + CycleCollectedJSRuntime* self = static_cast<CycleCollectedJSRuntime*>(aData); + + MOZ_ASSERT(CycleCollectedJSContext::Get()->Context() == aContext); + MOZ_ASSERT(CycleCollectedJSContext::Get()->Runtime() == self); + + self->OnOutOfMemory(); +} + +/* static */ +void* CycleCollectedJSRuntime::BeforeWaitCallback(uint8_t* aMemory) { + MOZ_ASSERT(aMemory); + + // aMemory is stack allocated memory to contain our RAII object. This allows + // for us to avoid allocations on the heap during this callback. + return new (aMemory) dom::AutoYieldJSThreadExecution; +} + +/* static */ +void CycleCollectedJSRuntime::AfterWaitCallback(void* aCookie) { + MOZ_ASSERT(aCookie); + static_cast<dom::AutoYieldJSThreadExecution*>(aCookie) + ->~AutoYieldJSThreadExecution(); +} + +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(nsWrapperCache* aPtr, const char* aName, + void* aClosure) const override { + aPtr->TraceWrapper(static_cast<JSTracer*>(aClosure), 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); +} + +#if defined(NIGHTLY_BUILD) || defined(MOZ_DEV_EDITION) || defined(DEBUG) +# define CHECK_SINGLE_ZONE_JS_HOLDERS +#endif + +#ifdef CHECK_SINGLE_ZONE_JS_HOLDERS + +// A tracer that checks that a JS holder only holds JS GC things in a single +// JS::Zone. +struct CheckZoneTracer : public TraceCallbacks { + const char* mClassName; + mutable JS::Zone* mZone; + + explicit CheckZoneTracer(const char* aClassName, JS::Zone* aZone = nullptr) + : mClassName(aClassName), mZone(aZone) {} + + void checkZone(JS::Zone* aZone, const char* aName) const { + if (JS::IsAtomsZone(aZone)) { + // Any holder may contain pointers into the atoms zone. + return; + } + + if (!mZone) { + mZone = aZone; + return; + } + + if (aZone == mZone) { + return; + } + + // Most JS holders only contain pointers to GC things in a single zone. We + // group holders by referent zone where possible, allowing us to improve GC + // performance by only tracing holders for zones that are being collected. + // + // Additionally, pointers from any holder into the atoms zone are allowed + // since all holders are traced when we collect the atoms zone. + // + // If you added a holder that has pointers into multiple zones do not + // use NS_IMPL_CYCLE_COLLECTION_SINGLE_ZONE_SCRIPT_HOLDER_CLASS. + MOZ_CRASH_UNSAFE_PRINTF( + "JS holder %s contains pointers to GC things in more than one zone (" + "found in %s)\n", + mClassName, aName); + } + + virtual void Trace(JS::Heap<JS::Value>* aPtr, const char* aName, + void* aClosure) const override { + JS::Value value = aPtr->unbarrieredGet(); + if (value.isGCThing()) { + checkZone(JS::GetGCThingZone(value.toGCCellPtr()), aName); + } + } + virtual void Trace(JS::Heap<jsid>* aPtr, const char* aName, + void* aClosure) const override { + jsid id = aPtr->unbarrieredGet(); + if (id.isGCThing()) { + MOZ_ASSERT(JS::IsAtomsZone(JS::GetTenuredGCThingZone(id.toGCCellPtr()))); + } + } + virtual void Trace(JS::Heap<JSObject*>* aPtr, const char* aName, + void* aClosure) const override { + JSObject* obj = aPtr->unbarrieredGet(); + if (obj) { + checkZone(js::GetObjectZoneFromAnyThread(obj), aName); + } + } + virtual void Trace(nsWrapperCache* aPtr, const char* aName, + void* aClosure) const override { + JSObject* obj = aPtr->GetWrapperPreserveColor(); + if (obj) { + checkZone(js::GetObjectZoneFromAnyThread(obj), aName); + } + } + virtual void Trace(JS::TenuredHeap<JSObject*>* aPtr, const char* aName, + void* aClosure) const override { + JSObject* obj = aPtr->unbarrieredGetPtr(); + if (obj) { + checkZone(js::GetObjectZoneFromAnyThread(obj), aName); + } + } + virtual void Trace(JS::Heap<JSString*>* aPtr, const char* aName, + void* aClosure) const override { + JSString* str = aPtr->unbarrieredGet(); + if (str) { + checkZone(JS::GetStringZone(str), aName); + } + } + virtual void Trace(JS::Heap<JSScript*>* aPtr, const char* aName, + void* aClosure) const override { + JSScript* script = aPtr->unbarrieredGet(); + if (script) { + checkZone(JS::GetTenuredGCThingZone(JS::GCCellPtr(script)), aName); + } + } + virtual void Trace(JS::Heap<JSFunction*>* aPtr, const char* aName, + void* aClosure) const override { + JSFunction* fun = aPtr->unbarrieredGet(); + if (fun) { + checkZone(js::GetObjectZoneFromAnyThread(JS_GetFunctionObject(fun)), + aName); + } + } +}; + +static inline void CheckHolderIsSingleZone( + void* aHolder, nsCycleCollectionParticipant* aParticipant, + JS::Zone* aZone) { + CheckZoneTracer tracer(aParticipant->ClassName(), aZone); + aParticipant->Trace(aHolder, tracer, nullptr); +} + +#endif + +static inline bool ShouldCheckSingleZoneHolders() { +#if defined(DEBUG) + return true; +#elif defined(NIGHTLY_BUILD) || defined(MOZ_DEV_EDITION) + // Don't check every time to avoid performance impact. + return rand() % 256 == 0; +#else + return false; +#endif +} + +#ifdef NS_BUILD_REFCNT_LOGGING +void CycleCollectedJSRuntime::TraceAllNativeGrayRoots(JSTracer* aTracer) { + MOZ_RELEASE_ASSERT(mHolderIter.isNothing()); + js::SliceBudget budget = js::SliceBudget::unlimited(); + MOZ_ALWAYS_TRUE( + TraceNativeGrayRoots(aTracer, JSHolderMap::AllHolders, budget)); +} +#endif + +bool CycleCollectedJSRuntime::TraceNativeGrayRoots( + JSTracer* aTracer, JSHolderMap::WhichHolders aWhich, + js::SliceBudget& aBudget) { + if (!mHolderIter) { + // 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); + + mHolderIter.emplace(mJSHolders, aWhich); + aBudget.forceCheck(); + } else { + // Holders may have been removed between slices, so we may need to update + // the iterator. + mHolderIter->UpdateForRemovals(); + } + + bool finished = TraceJSHolders(aTracer, *mHolderIter, aBudget); + if (finished) { + mHolderIter.reset(); + } + + return finished; +} + +bool CycleCollectedJSRuntime::TraceJSHolders(JSTracer* aTracer, + JSHolderMap::Iter& aIter, + js::SliceBudget& aBudget) { + bool checkSingleZoneHolders = ShouldCheckSingleZoneHolders(); + + while (!aIter.Done() && !aBudget.isOverBudget()) { + void* holder = aIter->mHolder; + nsScriptObjectTracer* tracer = aIter->mTracer; + +#ifdef CHECK_SINGLE_ZONE_JS_HOLDERS + if (checkSingleZoneHolders && tracer->IsSingleZoneJSHolder()) { + CheckHolderIsSingleZone(holder, tracer, aIter.Zone()); + } +#else + Unused << checkSingleZoneHolders; +#endif + + tracer->Trace(holder, JsGcTracer(), aTracer); + + aIter.Next(); + aBudget.step(); + } + + return aIter.Done(); +} + +void CycleCollectedJSRuntime::AddJSHolder(void* aHolder, + nsScriptObjectTracer* aTracer, + JS::Zone* aZone) { + mJSHolders.Put(aHolder, aTracer, aZone); +} + +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 = JS::PropertyKey::Void(); + } + + virtual void Trace(JS::Heap<JSObject*>* aPtr, const char*, + void*) const override { + *aPtr = nullptr; + } + + virtual void Trace(nsWrapperCache* aPtr, const char* aName, + void* aClosure) const override { + aPtr->ClearWrapper(); + } + + 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 CycleCollectedJSRuntime::RemoveJSHolder(void* aHolder) { + nsScriptObjectTracer* tracer = mJSHolders.Extract(aHolder); + if (tracer) { + // Bug 1531951: The analysis can't see through the virtual call but we know + // that the ClearJSHolder tracer will never GC. + JS::AutoSuppressGCAnalysis nogc; + tracer->Trace(aHolder, ClearJSHolder(), nullptr); + } +} + +#ifdef DEBUG +static void AssertNoGcThing(JS::GCCellPtr aGCThing, const char* aName, + void* aClosure) { + MOZ_ASSERT(!aGCThing); +} + +void CycleCollectedJSRuntime::AssertNoObjectsToTrace(void* aPossibleJSHolder) { + nsScriptObjectTracer* tracer = mJSHolders.Get(aPossibleJSHolder); + if (tracer) { + tracer->Trace(aPossibleJSHolder, TraceCallbackFunc(AssertNoGcThing), + nullptr); + } +} +#endif + +nsCycleCollectionParticipant* CycleCollectedJSRuntime::GCThingParticipant() { + return &mGCThingCycleCollectorGlobal; +} + +nsCycleCollectionParticipant* CycleCollectedJSRuntime::ZoneParticipant() { + return &mJSZoneCycleCollectorGlobal; +} + +nsresult CycleCollectedJSRuntime::TraverseRoots( + nsCycleCollectionNoteRootCallback& aCb) { + TraverseNativeRoots(aCb); + + NoteWeakMapsTracer trc(mJSRuntime, aCb); + js::TraceWeakMaps(&trc); + + return NS_OK; +} + +bool CycleCollectedJSRuntime::UsefulToMergeZones() const { return false; } + +void CycleCollectedJSRuntime::FixWeakMappingGrayBits() const { + MOZ_ASSERT(!JS::IsIncrementalGCInProgress(mJSRuntime), + "Don't call FixWeakMappingGrayBits during a GC."); + FixWeakMappingGrayBitsTracer fixer(mJSRuntime); + fixer.FixAll(); +} + +void CycleCollectedJSRuntime::CheckGrayBits() const { + MOZ_ASSERT(!JS::IsIncrementalGCInProgress(mJSRuntime), + "Don't call CheckGrayBits during a GC."); + +#ifndef ANDROID + // Bug 1346874 - The gray state check is expensive. Android tests are already + // slow enough that this check can easily push them over the threshold to a + // timeout. + + MOZ_ASSERT(js::CheckGrayMarkingState(mJSRuntime)); + MOZ_ASSERT(CheckWeakMappingGrayBitsTracer::Check(mJSRuntime)); +#endif +} + +bool CycleCollectedJSRuntime::AreGCGrayBitsValid() const { + return js::AreGCGrayBitsValid(mJSRuntime); +} + +void CycleCollectedJSRuntime::GarbageCollect(JS::GCOptions aOptions, + JS::GCReason aReason) const { + JSContext* cx = CycleCollectedJSContext::Get()->Context(); + JS::PrepareForFullGC(cx); + JS::NonIncrementalGC(cx, aOptions, aReason); +} + +void CycleCollectedJSRuntime::JSObjectsTenured() { + JSContext* cx = CycleCollectedJSContext::Get()->Context(); + for (auto iter = mNurseryObjects.Iter(); !iter.Done(); iter.Next()) { + nsWrapperCache* cache = iter.Get(); + JSObject* wrapper = cache->GetWrapperMaybeDead(); + MOZ_DIAGNOSTIC_ASSERT(wrapper); + if (!JS::ObjectIsTenured(wrapper)) { + MOZ_ASSERT(!cache->PreservingWrapper()); + js::gc::FinalizeDeadNurseryObject(cx, wrapper); + } + } + + mNurseryObjects.Clear(); +} + +void CycleCollectedJSRuntime::NurseryWrapperAdded(nsWrapperCache* aCache) { + MOZ_ASSERT(aCache); + MOZ_ASSERT(aCache->GetWrapperMaybeDead()); + MOZ_ASSERT(!JS::ObjectIsTenured(aCache->GetWrapperMaybeDead())); + mNurseryObjects.InfallibleAppend(aCache); +} + +void CycleCollectedJSRuntime::DeferredFinalize( + DeferredFinalizeAppendFunction aAppendFunc, DeferredFinalizeFunction aFunc, + void* aThing) { + // Tell the analysis that the function pointers will not GC. + JS::AutoSuppressGCAnalysis suppress; + mDeferredFinalizerTable.WithEntryHandle(aFunc, [&](auto&& entry) { + if (entry) { + aAppendFunc(entry.Data(), aThing); + } else { + entry.Insert(aAppendFunc(nullptr, aThing)); + } + }); +} + +void CycleCollectedJSRuntime::DeferredFinalize(nsISupports* aSupports) { + typedef DeferredFinalizerImpl<nsISupports> Impl; + DeferredFinalize(Impl::AppendDeferredFinalizePointer, Impl::DeferredFinalize, + aSupports); +} + +void CycleCollectedJSRuntime::DumpJSHeap(FILE* aFile) { + JSContext* cx = CycleCollectedJSContext::Get()->Context(); + + mozilla::MallocSizeOf mallocSizeOf = + PR_GetEnv("MOZ_GC_LOG_SIZE") ? moz_malloc_size_of : nullptr; + js::DumpHeap(cx, aFile, js::CollectNurseryBeforeDump, mallocSizeOf); +} + +IncrementalFinalizeRunnable::IncrementalFinalizeRunnable( + CycleCollectedJSRuntime* aRt, DeferredFinalizerTable& aFinalizers) + : DiscardableRunnable("IncrementalFinalizeRunnable"), + mRuntime(aRt), + 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(); + } + MOZ_ASSERT(mDeferredFinalizeFunctions.Length()); +} + +IncrementalFinalizeRunnable::~IncrementalFinalizeRunnable() { + MOZ_ASSERT(!mDeferredFinalizeFunctions.Length()); + MOZ_ASSERT(!mRuntime); +} + +void IncrementalFinalizeRunnable::ReleaseNow(bool aLimited) { + if (mReleasing) { + NS_WARNING("Re-entering ReleaseNow"); + return; + } + { + AUTO_PROFILER_LABEL("IncrementalFinalizeRunnable::ReleaseNow", + GCCC_Finalize); + + 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 = aLimited ? TimeStamp::Now() : TimeStamp(); + 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(mRuntime->mFinalizeRunnable == this); + mDeferredFinalizeFunctions.Clear(); + CycleCollectedJSRuntime* runtime = mRuntime; + mRuntime = nullptr; + // NB: This may delete this! + runtime->mFinalizeRunnable = nullptr; + } +} + +NS_IMETHODIMP +IncrementalFinalizeRunnable::Run() { + if (!mDeferredFinalizeFunctions.Length()) { + /* These items were already processed synchronously in JSGC_END. */ + MOZ_ASSERT(!mRuntime); + return NS_OK; + } + + MOZ_ASSERT(mRuntime->mFinalizeRunnable == this); + TimeStamp start = TimeStamp::Now(); + ReleaseNow(true); + + if (mDeferredFinalizeFunctions.Length()) { + nsresult rv = NS_DispatchToCurrentThread(this); + if (NS_FAILED(rv)) { + ReleaseNow(false); + } + } else { + MOZ_ASSERT(!mRuntime); + } + + uint32_t duration = (uint32_t)((TimeStamp::Now() - start).ToMilliseconds()); + Telemetry::Accumulate(Telemetry::DEFERRED_FINALIZE_ASYNC, duration); + + return NS_OK; +} + +void CycleCollectedJSRuntime::FinalizeDeferredThings( + DeferredFinalizeType aType) { + // If mFinalizeRunnable isn't null, we didn't finalize everything from the + // previous GC. + if (mFinalizeRunnable) { + if (aType == FinalizeLater) { + // We need to defer all finalization until we return to the event loop, + // so leave things alone. Any new objects to be finalized from the current + // GC will be handled by the existing mFinalizeRunnable. + return; + } + MOZ_ASSERT(aType == FinalizeIncrementally || aType == FinalizeNow); + // If we're finalizing incrementally, we don't want finalizers to build up, + // so try to finish them off now. + // If we're finalizing synchronously, also go ahead and clear them out, + // so we make sure as much as possible is freed. + 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 there's nothing to finalize, don't create a new runnable. + if (mDeferredFinalizerTable.Count() == 0) { + return; + } + + mFinalizeRunnable = + new IncrementalFinalizeRunnable(this, mDeferredFinalizerTable); + + // Everything should be gone now. + MOZ_ASSERT(mDeferredFinalizerTable.Count() == 0); + + if (aType == FinalizeNow) { + mFinalizeRunnable->ReleaseNow(false); + MOZ_ASSERT(!mFinalizeRunnable); + } else { + MOZ_ASSERT(aType == FinalizeIncrementally || aType == FinalizeLater); + NS_DispatchToCurrentThreadQueue(do_AddRef(mFinalizeRunnable), 2500, + EventQueuePriority::Idle); + } +} + +const char* CycleCollectedJSRuntime::OOMStateToString( + const OOMState aOomState) const { + switch (aOomState) { + case OOMState::OK: + return "OK"; + case OOMState::Reporting: + return "Reporting"; + case OOMState::Reported: + return "Reported"; + case OOMState::Recovered: + return "Recovered"; + default: + MOZ_ASSERT_UNREACHABLE("OOMState holds an invalid value"); + return "Unknown"; + } +} + +bool CycleCollectedJSRuntime::OOMReported() { + return mOutOfMemoryState == OOMState::Reported; +} + +void CycleCollectedJSRuntime::AnnotateAndSetOutOfMemory(OOMState* aStatePtr, + OOMState aNewState) { + *aStatePtr = aNewState; + CrashReporter::Annotation annotation = + (aStatePtr == &mOutOfMemoryState) + ? CrashReporter::Annotation::JSOutOfMemory + : CrashReporter::Annotation::JSLargeAllocationFailure; + + CrashReporter::AnnotateCrashReport( + annotation, nsDependentCString(OOMStateToString(aNewState))); +} + +void CycleCollectedJSRuntime::OnGC(JSContext* aContext, JSGCStatus aStatus, + JS::GCReason aReason) { + switch (aStatus) { + case JSGC_BEGIN: + MOZ_RELEASE_ASSERT(mHolderIter.isNothing()); + nsCycleCollector_prepareForGarbageCollection(); + PrepareWaitingZonesForGC(); + break; + case JSGC_END: { + MOZ_RELEASE_ASSERT(mHolderIter.isNothing()); + if (mOutOfMemoryState == OOMState::Reported) { + AnnotateAndSetOutOfMemory(&mOutOfMemoryState, OOMState::Recovered); + } + if (mLargeAllocationFailureState == OOMState::Reported) { + AnnotateAndSetOutOfMemory(&mLargeAllocationFailureState, + OOMState::Recovered); + } + + DeferredFinalizeType finalizeType; + if (JS_IsExceptionPending(aContext)) { + // There is a pending exception. The finalizers are not set up to run + // in that state, so don't run the finalizer until we've returned to the + // event loop. + finalizeType = FinalizeLater; + } else if (JS::InternalGCReason(aReason)) { + if (aReason == JS::GCReason::DESTROY_RUNTIME) { + // We're shutting down, so we need to destroy things immediately. + finalizeType = FinalizeNow; + } else { + // We may be in the middle of running some code that the JIT has + // assumed can't have certain kinds of side effects. Finalizers can do + // all sorts of things, such as run JS, so we want to run them later, + // after we've returned to the event loop. + finalizeType = FinalizeLater; + } + } else if (JS::WasIncrementalGC(mJSRuntime)) { + // The GC was incremental, so we probably care about pauses. Try to + // break up finalization, but it is okay if we do some now. + finalizeType = FinalizeIncrementally; + } else { + // If we're running a synchronous GC, we probably want to free things as + // quickly as possible. This can happen during testing or if memory is + // low. + finalizeType = FinalizeNow; + } + FinalizeDeferredThings(finalizeType); + + break; + } + default: + MOZ_CRASH(); + } + + CustomGCCallback(aStatus); +} + +void CycleCollectedJSRuntime::OnOutOfMemory() { + AnnotateAndSetOutOfMemory(&mOutOfMemoryState, OOMState::Reporting); + CustomOutOfMemoryCallback(); + AnnotateAndSetOutOfMemory(&mOutOfMemoryState, OOMState::Reported); +} + +void CycleCollectedJSRuntime::SetLargeAllocationFailure(OOMState aNewState) { + AnnotateAndSetOutOfMemory(&mLargeAllocationFailureState, aNewState); +} + +void CycleCollectedJSRuntime::PrepareWaitingZonesForGC() { + JSContext* cx = CycleCollectedJSContext::Get()->Context(); + if (mZonesWaitingForGC.Count() == 0) { + JS::PrepareForFullGC(cx); + } else { + for (const auto& key : mZonesWaitingForGC) { + JS::PrepareZoneForGC(cx, key); + } + mZonesWaitingForGC.Clear(); + } +} + +/* static */ +void CycleCollectedJSRuntime::OnZoneDestroyed(JS::GCContext* aGcx, + JS::Zone* aZone) { + // Remove the zone from the set of zones waiting for GC, if present. This can + // happen if a zone is added to the set during an incremental GC in which it + // is later destroyed. + CycleCollectedJSRuntime* runtime = Get(); + runtime->mZonesWaitingForGC.Remove(aZone); +} + +void CycleCollectedJSRuntime::EnvironmentPreparer::invoke( + JS::HandleObject global, js::ScriptEnvironmentPreparer::Closure& closure) { + MOZ_ASSERT(JS_IsGlobalObject(global)); + nsIGlobalObject* nativeGlobal = xpc::NativeGlobal(global); + + // Not much we can do if we simply don't have a usable global here... + NS_ENSURE_TRUE_VOID(nativeGlobal && nativeGlobal->HasJSGlobal()); + + AutoEntryScript aes(nativeGlobal, "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. +} + +/* static */ +CycleCollectedJSRuntime* CycleCollectedJSRuntime::Get() { + auto context = CycleCollectedJSContext::Get(); + if (context) { + return context->Runtime(); + } + return nullptr; +} + +#ifdef MOZ_JS_DEV_ERROR_INTERCEPTOR + +namespace js { +extern void DumpValue(const JS::Value& val); +} + +void CycleCollectedJSRuntime::ErrorInterceptor::Shutdown(JSRuntime* rt) { + JS_SetErrorInterceptorCallback(rt, nullptr); + mThrownError.reset(); +} + +/* virtual */ +void CycleCollectedJSRuntime::ErrorInterceptor::interceptError( + JSContext* cx, JS::HandleValue exn) { + if (mThrownError) { + // We already have an error, we don't need anything more. + return; + } + + if (!nsContentUtils::ThreadsafeIsSystemCaller(cx)) { + // We are only interested in chrome code. + return; + } + + const auto type = JS_GetErrorType(exn); + if (!type) { + // This is not one of the primitive error types. + return; + } + + switch (*type) { + case JSExnType::JSEXN_REFERENCEERR: + case JSExnType::JSEXN_SYNTAXERR: + break; + default: + // Not one of the errors we are interested in. + // Note that we are not interested in instances of `TypeError` + // for the time being, as DOM (ab)uses this constructor to represent + // all sorts of errors that are not even remotely related to type + // errors (e.g. some network errors). + // If we ever have a mechanism to differentiate between DOM-thrown + // and SpiderMonkey-thrown instances of `TypeError`, we should + // consider watching for `TypeError` here. + return; + } + + // Now copy the details of the exception locally. + // While copying the details of an exception could be expensive, in most runs, + // this will be done at most once during the execution of the process, so the + // total cost should be reasonable. + + ErrorDetails details; + details.mType = *type; + // If `exn` isn't an exception object, `ExtractErrorValues` could end up + // calling `toString()`, which could in turn end up throwing an error. While + // this should work, we want to avoid that complex use case. Fortunately, we + // have already checked above that `exn` is an exception object, so nothing + // such should happen. + nsContentUtils::ExtractErrorValues(cx, exn, details.mFilename, &details.mLine, + &details.mColumn, details.mMessage); + + JS::UniqueChars buf = + JS::FormatStackDump(cx, /* showArgs = */ false, /* showLocals = */ false, + /* showThisProps = */ false); + CopyUTF8toUTF16(mozilla::MakeStringSpan(buf.get()), details.mStack); + + mThrownError.emplace(std::move(details)); +} + +void CycleCollectedJSRuntime::ClearRecentDevError() { + mErrorInterceptor.mThrownError.reset(); +} + +bool CycleCollectedJSRuntime::GetRecentDevError( + JSContext* cx, JS::MutableHandle<JS::Value> error) { + if (!mErrorInterceptor.mThrownError) { + return true; + } + + // Create a copy of the exception. + JS::RootedObject obj(cx, JS_NewPlainObject(cx)); + if (!obj) { + return false; + } + + JS::RootedValue message(cx); + JS::RootedValue filename(cx); + JS::RootedValue stack(cx); + if (!ToJSValue(cx, mErrorInterceptor.mThrownError->mMessage, &message) || + !ToJSValue(cx, mErrorInterceptor.mThrownError->mFilename, &filename) || + !ToJSValue(cx, mErrorInterceptor.mThrownError->mStack, &stack)) { + return false; + } + + // Build the object. + const auto FLAGS = JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT; + if (!JS_DefineProperty(cx, obj, "message", message, FLAGS) || + !JS_DefineProperty(cx, obj, "fileName", filename, FLAGS) || + !JS_DefineProperty(cx, obj, "lineNumber", + mErrorInterceptor.mThrownError->mLine, FLAGS) || + !JS_DefineProperty(cx, obj, "stack", stack, FLAGS)) { + return false; + } + + // Pass the result. + error.setObject(*obj); + return true; +} +#endif // MOZ_JS_DEV_ERROR_INTERCEPTOR + +#undef MOZ_JS_DEV_ERROR_INTERCEPTOR diff --git a/xpcom/base/CycleCollectedJSRuntime.h b/xpcom/base/CycleCollectedJSRuntime.h new file mode 100644 index 0000000000..6f03d3ee99 --- /dev/null +++ b/xpcom/base/CycleCollectedJSRuntime.h @@ -0,0 +1,526 @@ +/* -*- 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_CycleCollectedJSRuntime_h +#define mozilla_CycleCollectedJSRuntime_h + +#include "mozilla/CycleCollectedJSContext.h" +#include "mozilla/DeferredFinalize.h" +#include "mozilla/HashTable.h" +#include "mozilla/Maybe.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/RefPtr.h" +#include "mozilla/SegmentedVector.h" +#include "jsapi.h" +#include "jsfriendapi.h" +#include "js/TypeDecls.h" + +#include "nsCycleCollectionParticipant.h" +#include "nsTHashMap.h" +#include "nsHashKeys.h" +#include "nsStringFwd.h" +#include "nsTHashSet.h" + +class nsCycleCollectionNoteRootCallback; +class nsIException; +class nsWrapperCache; + +namespace mozilla { + +class JSGCThingParticipant : public nsCycleCollectionParticipant { + public: + constexpr JSGCThingParticipant() : nsCycleCollectionParticipant(false) {} + + 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 TraverseNative(void* aPtr, + nsCycleCollectionTraversalCallback& aCb) override; + + NS_DECL_CYCLE_COLLECTION_CLASS_NAME_METHOD(JSGCThingParticipant) +}; + +class JSZoneParticipant : public nsCycleCollectionParticipant { + public: + constexpr JSZoneParticipant() : nsCycleCollectionParticipant(false) {} + + 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 TraverseNative(void* aPtr, + nsCycleCollectionTraversalCallback& aCb) override; + + NS_DECL_CYCLE_COLLECTION_CLASS_NAME_METHOD(JSZoneParticipant) +}; + +class IncrementalFinalizeRunnable; + +// A map from JS holders to tracer objects, where the values are stored in +// SegmentedVector to speed up iteration. +class JSHolderMap { + public: + enum WhichHolders { AllHolders, HoldersRequiredForGrayMarking }; + + class Iter; + + JSHolderMap(); + ~JSHolderMap() { MOZ_RELEASE_ASSERT(!mHasIterator); } + + bool Has(void* aHolder) const; + nsScriptObjectTracer* Get(void* aHolder) const; + nsScriptObjectTracer* Extract(void* aHolder); + void Put(void* aHolder, nsScriptObjectTracer* aTracer, JS::Zone* aZone); + + size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const; + + private: + struct Entry { + void* mHolder; + nsScriptObjectTracer* mTracer; +#ifdef DEBUG + JS::Zone* mZone; +#endif + + Entry(); + Entry(void* aHolder, nsScriptObjectTracer* aTracer, JS::Zone* aZone); + }; + + using EntryMap = mozilla::HashMap<void*, Entry*, DefaultHasher<void*>, + InfallibleAllocPolicy>; + + using EntryVector = SegmentedVector<Entry, 256, InfallibleAllocPolicy>; + + using EntryVectorMap = + mozilla::HashMap<JS::Zone*, UniquePtr<EntryVector>, + DefaultHasher<JS::Zone*>, InfallibleAllocPolicy>; + + class EntryVectorIter; + + bool RemoveEntry(EntryVector& aJSHolders, Entry* aEntry); + + // A map from a holder pointer to a pointer to an entry in a vector. + EntryMap mJSHolderMap; + + // A vector of holders not associated with a particular zone or that can + // contain pointers to GC things in more than one zone. + EntryVector mAnyZoneJSHolders; + + // A map from a zone to a vector of holders that only contain pointers to GC + // things in that zone. + // + // Currently this will only contain wrapper cache wrappers since these are the + // only holders to pass a zone parameter through to AddJSHolder. + EntryVectorMap mPerZoneJSHolders; + + // Iterators can mutate the element vectors by removing stale elements. Allow + // at most one to exist at a time. + bool mHasIterator = false; +}; + +// An iterator over an EntryVector that skips over removed entries and removes +// them from the map. +class JSHolderMap::EntryVectorIter { + public: + EntryVectorIter(JSHolderMap& aMap, EntryVector& aVector) + : mHolderMap(aMap), mVector(aVector), mIter(aVector.Iter()) { + Settle(); + } + + const EntryVector& Vector() const { return mVector; } + + bool Done() const { return mIter.Done(); } + const Entry& Get() const { return mIter.Get(); } + void Next() { + mIter.Next(); + Settle(); + } + + operator const Entry*() const { return &Get(); } + const Entry* operator->() const { return &Get(); } + + private: + void Settle(); + friend class JSHolderMap::Iter; + + JSHolderMap& mHolderMap; + EntryVector& mVector; + EntryVector::IterImpl mIter; +}; + +class JSHolderMap::Iter { + public: + explicit Iter(JSHolderMap& aMap, WhichHolders aWhich = AllHolders); + + ~Iter() { + MOZ_RELEASE_ASSERT(mHolderMap.mHasIterator); + mHolderMap.mHasIterator = false; + } + + bool Done() const { return mIter.Done(); } + const Entry& Get() const { return mIter.Get(); } + void Next() { + mIter.Next(); + Settle(); + } + + // If the holders have been removed from the map while the iterator is live, + // then the iterator may point to a removed entry. Update the iterator to make + // sure it points to a valid entry or is done. + void UpdateForRemovals(); + + operator const Entry*() const { return &Get(); } + const Entry* operator->() const { return &Get(); } + + JS::Zone* Zone() const { return mZone; } + + private: + void Settle(); + + JSHolderMap& mHolderMap; + Vector<JS::Zone*, 1, InfallibleAllocPolicy> mZones; + JS::Zone* mZone = nullptr; + EntryVectorIter mIter; +}; + +class CycleCollectedJSRuntime { + friend class JSGCThingParticipant; + friend class JSZoneParticipant; + friend class IncrementalFinalizeRunnable; + friend class CycleCollectedJSContext; + + protected: + CycleCollectedJSRuntime(JSContext* aMainContext); + virtual ~CycleCollectedJSRuntime(); + + virtual void Shutdown(JSContext* cx); + + 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() {} + + CycleCollectedJSContext* GetContext() { return mContext; } + + private: + void DescribeGCThing(bool aIsMarked, JS::GCCellPtr aThing, + nsCycleCollectionTraversalCallback& aCb) const; + + virtual bool DescribeCustomObjects(JSObject* aObject, const JSClass* aClasp, + char (&aName)[72]) const { + return false; // We did nothing. + } + + void NoteGCThingJSChildren(JS::GCCellPtr aThing, + nsCycleCollectionTraversalCallback& aCb) const; + + void NoteGCThingXPCOMChildren(const JSClass* aClasp, JSObject* aObj, + nsCycleCollectionTraversalCallback& aCb) const; + + virtual bool NoteCustomGCThingXPCOMChildren( + const JSClass* 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, + const JS::AutoRequireNoGC& nogc); + + void TraverseNativeRoots(nsCycleCollectionNoteRootCallback& aCb); + + static void TraceBlackJS(JSTracer* aTracer, void* aData); + + // Trace gray JS roots until budget is exceeded and return whether we + // finished. + static bool TraceGrayJS(JSTracer* aTracer, js::SliceBudget& budget, + void* aData); + + static void GCCallback(JSContext* aContext, JSGCStatus aStatus, + JS::GCReason aReason, void* aData); + static void GCSliceCallback(JSContext* aContext, JS::GCProgress aProgress, + const JS::GCDescription& aDesc); + static void GCNurseryCollectionCallback(JSContext* aContext, + JS::GCNurseryProgress aProgress, + JS::GCReason aReason, void* data); + static void OutOfMemoryCallback(JSContext* aContext, void* aData); + + static bool ContextCallback(JSContext* aCx, unsigned aOperation, void* aData); + + static void* BeforeWaitCallback(uint8_t* aMemory); + static void AfterWaitCallback(void* aCookie); + + virtual void TraceNativeBlackRoots(JSTracer* aTracer){}; + +#ifdef NS_BUILD_REFCNT_LOGGING + void TraceAllNativeGrayRoots(JSTracer* aTracer); +#endif + + bool TraceNativeGrayRoots(JSTracer* aTracer, JSHolderMap::WhichHolders aWhich, + js::SliceBudget& aBudget); + bool TraceJSHolders(JSTracer* aTracer, JSHolderMap::Iter& aIter, + js::SliceBudget& aBudget); + + public: + enum DeferredFinalizeType { + // Never finalize immediately, because it would be unsafe. + FinalizeLater, + // Finalize later if we can, but it is okay to do it immediately. + FinalizeIncrementally, + // Finalize immediately, for shutdown or testing purposes. + FinalizeNow, + }; + + void FinalizeDeferredThings(DeferredFinalizeType aType); + + virtual void PrepareForForgetSkippable() = 0; + virtual void BeginCycleCollectionCallback(mozilla::CCReason aReason) = 0; + virtual void EndCycleCollectionCallback(CycleCollectorResults& aResults) = 0; + virtual void DispatchDeferredDeletion(bool aContinuation, + bool aPurge = false) = 0; + + // 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 + }; + + const char* OOMStateToString(const OOMState aOomState) const; + + // Returns true if OOM was reported and a new successful GC cycle hasn't + // occurred since. + bool OOMReported(); + + void SetLargeAllocationFailure(OOMState aNewState); + + void AnnotateAndSetOutOfMemory(OOMState* aStatePtr, OOMState aNewState); + void OnGC(JSContext* aContext, JSGCStatus aStatus, JS::GCReason aReason); + void OnOutOfMemory(); + void OnLargeAllocationFailure(); + + JSRuntime* Runtime() { return mJSRuntime; } + const JSRuntime* Runtime() const { return mJSRuntime; } + + bool HasPendingIdleGCTask() const { + // Idle GC task associates with JSRuntime. + MOZ_ASSERT_IF(mHasPendingIdleGCTask, Runtime()); + return mHasPendingIdleGCTask; + } + void SetPendingIdleGCTask() { + // Idle GC task associates with JSRuntime. + MOZ_ASSERT(Runtime()); + mHasPendingIdleGCTask = true; + } + void ClearPendingIdleGCTask() { mHasPendingIdleGCTask = false; } + + void RunIdleTimeGCTask() { + if (HasPendingIdleGCTask()) { + JS::MaybeRunNurseryCollection(Runtime(), + JS::GCReason::EAGER_NURSERY_COLLECTION); + ClearPendingIdleGCTask(); + } + } + + bool IsIdleGCTaskNeeded() { + return !HasPendingIdleGCTask() && Runtime() && + JS::WantEagerMinorGC(Runtime()) != JS::GCReason::NO_REASON; + } + + public: + void AddJSHolder(void* aHolder, nsScriptObjectTracer* aTracer, + JS::Zone* aZone); + void RemoveJSHolder(void* aHolder); +#ifdef DEBUG + void AssertNoObjectsToTrace(void* aPossibleJSHolder); +#endif + + nsCycleCollectionParticipant* GCThingParticipant(); + nsCycleCollectionParticipant* ZoneParticipant(); + + nsresult TraverseRoots(nsCycleCollectionNoteRootCallback& aCb); + virtual bool UsefulToMergeZones() const; + void FixWeakMappingGrayBits() const; + void CheckGrayBits() const; + bool AreGCGrayBitsValid() const; + void GarbageCollect(JS::GCOptions options, JS::GCReason aReason) const; + + // This needs to be an nsWrapperCache, not a JSObject, because we need to know + // when our object gets moved. But we can't trace it (and hence update our + // storage), because we do not want to keep it alive. nsWrapperCache handles + // this for us via its "object moved" handling. + void NurseryWrapperAdded(nsWrapperCache* aCache); + void JSObjectsTenured(); + + void DeferredFinalize(DeferredFinalizeAppendFunction aAppendFunc, + DeferredFinalizeFunction aFunc, void* aThing); + void DeferredFinalize(nsISupports* aSupports); + + void DumpJSHeap(FILE* aFile); + + // Add aZone to the set of zones waiting for a GC. + void AddZoneWaitingForGC(JS::Zone* aZone) { + mZonesWaitingForGC.Insert(aZone); + } + + static void OnZoneDestroyed(JS::GCContext* aGcx, JS::Zone* 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(); + + // Get the current thread's CycleCollectedJSRuntime. Returns null if there + // isn't one. + static CycleCollectedJSRuntime* Get(); + + void SetContext(CycleCollectedJSContext* aContext); + +#ifdef NIGHTLY_BUILD + bool GetRecentDevError(JSContext* aContext, + JS::MutableHandle<JS::Value> aError); + void ClearRecentDevError(); +#endif // defined(NIGHTLY_BUILD) + + private: + CycleCollectedJSContext* mContext; + + JSGCThingParticipant mGCThingCycleCollectorGlobal; + + JSZoneParticipant mJSZoneCycleCollectorGlobal; + + JSRuntime* mJSRuntime; + bool mHasPendingIdleGCTask; + + JS::GCSliceCallback mPrevGCSliceCallback; + + mozilla::TimeStamp mLatestNurseryCollectionStart; + + JSHolderMap mJSHolders; + Maybe<JSHolderMap::Iter> mHolderIter; + + using DeferredFinalizerTable = nsTHashMap<DeferredFinalizeFunction, void*>; + DeferredFinalizerTable mDeferredFinalizerTable; + + RefPtr<IncrementalFinalizeRunnable> mFinalizeRunnable; + + OOMState mOutOfMemoryState; + OOMState mLargeAllocationFailureState; + + static const size_t kSegmentSize = 512; + SegmentedVector<nsWrapperCache*, kSegmentSize, InfallibleAllocPolicy> + mNurseryObjects; + + nsTHashSet<JS::Zone*> mZonesWaitingForGC; + + struct EnvironmentPreparer : public js::ScriptEnvironmentPreparer { + void invoke(JS::Handle<JSObject*> global, Closure& closure) override; + }; + EnvironmentPreparer mEnvironmentPreparer; + +#ifdef DEBUG + bool mShutdownCalled; +#endif + +#ifdef NIGHTLY_BUILD + // Implementation of the error interceptor. + // Built on nightly only to avoid any possible performance impact on release + + struct ErrorInterceptor final : public JSErrorInterceptor { + virtual void interceptError(JSContext* cx, + JS::Handle<JS::Value> exn) override; + void Shutdown(JSRuntime* rt); + + // Copy of the details of the exception. + // We store this rather than the exception itself to avoid dealing with + // complicated garbage-collection scenarios, e.g. a JSContext being killed + // while we still hold onto an exception thrown from it. + struct ErrorDetails { + nsString mFilename; + nsString mMessage; + nsString mStack; + JSExnType mType; + uint32_t mLine; + uint32_t mColumn; + }; + + // If we have encountered at least one developer error, + // the first error we have encountered. Otherwise, or + // if we have reset since the latest error, `None`. + Maybe<ErrorDetails> mThrownError; + }; + ErrorInterceptor mErrorInterceptor; + +#endif // defined(NIGHTLY_BUILD) +}; + +void TraceScriptHolder(nsISupports* aHolder, JSTracer* aTracer); + +} // namespace mozilla + +#endif // mozilla_CycleCollectedJSRuntime_h diff --git a/xpcom/base/Debug.cpp b/xpcom/base/Debug.cpp new file mode 100644 index 0000000000..7e2a4c1d10 --- /dev/null +++ b/xpcom/base/Debug.cpp @@ -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/. */ + +#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 0000000000..1d6eaaf9a4 --- /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 0000000000..f8415dc3d8 --- /dev/null +++ b/xpcom/base/DebuggerOnGCRunnable.cpp @@ -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/. */ + +#include "mozilla/DebuggerOnGCRunnable.h" + +#include <utility> + +#include "js/Debug.h" +#include "mozilla/CycleCollectedJSContext.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/SchedulerGroup.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(std::move(gcEvent)); + return NS_DispatchToCurrentThread(runOnGC); +} + +NS_IMETHODIMP +DebuggerOnGCRunnable::Run() { + dom::AutoJSAPI jsapi; + jsapi.Init(); + if (!JS::dbg::FireOnGarbageCollectionHook(jsapi.cx(), std::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 0000000000..93ac48b56c --- /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 <utility> + +#include "js/GCAPI.h" +#include "mozilla/UniquePtr.h" +#include "nsThreadUtils.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) + : CancelableRunnable("DebuggerOnGCRunnable"), + mGCData(std::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 0000000000..aea034dbf3 --- /dev/null +++ b/xpcom/base/DeferredFinalize.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/DeferredFinalize.h" + +#include "mozilla/Assertions.h" +#include "mozilla/CycleCollectedJSRuntime.h" + +void mozilla::DeferredFinalize(nsISupports* aSupports) { + CycleCollectedJSRuntime* rt = CycleCollectedJSRuntime::Get(); + MOZ_ASSERT(rt, "Should have a CycleCollectedJSRuntime by now"); + rt->DeferredFinalize(aSupports); +} + +void mozilla::DeferredFinalize(DeferredFinalizeAppendFunction aAppendFunc, + DeferredFinalizeFunction aFunc, void* aThing) { + CycleCollectedJSRuntime* rt = CycleCollectedJSRuntime::Get(); + MOZ_ASSERT(rt, "Should have a CycleCollectedJSRuntime by now"); + rt->DeferredFinalize(aAppendFunc, aFunc, aThing); +} diff --git a/xpcom/base/DeferredFinalize.h b/xpcom/base/DeferredFinalize.h new file mode 100644 index 0000000000..8b0e7e28be --- /dev/null +++ b/xpcom/base/DeferredFinalize.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_DeferredFinalize_h +#define mozilla_DeferredFinalize_h + +#include <cstdint> +#include "mozilla/Attributes.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); + +MOZ_NEVER_INLINE void DeferredFinalize(nsISupports* aSupports); + +} // namespace mozilla + +#endif // mozilla_DeferredFinalize_h diff --git a/xpcom/base/EnumeratedArrayCycleCollection.h b/xpcom/base/EnumeratedArrayCycleCollection.h new file mode 100644 index 0000000000..465d4cf38a --- /dev/null +++ b/xpcom/base/EnumeratedArrayCycleCollection.h @@ -0,0 +1,32 @@ +/* -*- 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 EnumeratedArrayCycleCollection_h_ +#define EnumeratedArrayCycleCollection_h_ + +#include "mozilla/EnumeratedArray.h" +#include "nsCycleCollectionTraversalCallback.h" + +template <typename IndexType, IndexType SizeAsEnumValue, typename ValueType> +inline void ImplCycleCollectionUnlink( + mozilla::EnumeratedArray<IndexType, SizeAsEnumValue, ValueType>& aField) { + for (size_t i = 0; i < size_t(SizeAsEnumValue); ++i) { + aField[IndexType(i)] = nullptr; + } +} + +template <typename IndexType, IndexType SizeAsEnumValue, typename ValueType> +inline void ImplCycleCollectionTraverse( + nsCycleCollectionTraversalCallback& aCallback, + mozilla::EnumeratedArray<IndexType, SizeAsEnumValue, ValueType>& aField, + const char* aName, uint32_t aFlags = 0) { + aFlags |= CycleCollectionEdgeNameArrayFlag; + for (size_t i = 0; i < size_t(SizeAsEnumValue); ++i) { + ImplCycleCollectionTraverse(aCallback, aField[IndexType(i)], aName, aFlags); + } +} + +#endif // EnumeratedArrayCycleCollection_h_ diff --git a/xpcom/base/ErrorList.py b/xpcom/base/ErrorList.py new file mode 100755 index 0000000000..4446dc9cc0 --- /dev/null +++ b/xpcom/base/ErrorList.py @@ -0,0 +1,1425 @@ +#!/usr/bin/env python +from collections import OrderedDict + + +class Mod: + """ + A nserror module. When used with a `with` statement, binds the itself to + Mod.active. + """ + + active = None + + def __init__(self, num): + self.num = num + + def __enter__(self): + Mod.active = self + + def __exit__(self, _type, _value, _traceback): + Mod.active = None + + +modules = OrderedDict() + +# 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. + +modules["XPCOM"] = Mod(1) +modules["BASE"] = Mod(2) +modules["GFX"] = Mod(3) +modules["WIDGET"] = Mod(4) +modules["CALENDAR"] = Mod(5) +modules["NETWORK"] = Mod(6) +modules["PLUGINS"] = Mod(7) +modules["LAYOUT"] = Mod(8) +modules["HTMLPARSER"] = Mod(9) +modules["RDF"] = Mod(10) +modules["UCONV"] = Mod(11) +modules["REG"] = Mod(12) +modules["FILES"] = Mod(13) +modules["DOM"] = Mod(14) +modules["IMGLIB"] = Mod(15) +modules["MAILNEWS"] = Mod(16) +modules["EDITOR"] = Mod(17) +modules["XPCONNECT"] = Mod(18) +modules["PROFILE"] = Mod(19) +modules["LDAP"] = Mod(20) +modules["SECURITY"] = Mod(21) +modules["DOM_XPATH"] = Mod(22) +# Mod(23) used to be NS_ERROR_MODULE_DOM_RANGE (see bug 711047) +modules["URILOADER"] = Mod(24) +modules["CONTENT"] = Mod(25) +modules["PYXPCOM"] = Mod(26) +modules["XSLT"] = Mod(27) +modules["IPC"] = Mod(28) +modules["SVG"] = Mod(29) +modules["STORAGE"] = Mod(30) +modules["SCHEMA"] = Mod(31) +modules["DOM_FILE"] = Mod(32) +modules["DOM_INDEXEDDB"] = Mod(33) +modules["DOM_FILEHANDLE"] = Mod(34) +modules["SIGNED_JAR"] = Mod(35) +modules["DOM_FILESYSTEM"] = Mod(36) +modules["DOM_BLUETOOTH"] = Mod(37) +modules["SIGNED_APP"] = Mod(38) +modules["DOM_ANIM"] = Mod(39) +modules["DOM_PUSH"] = Mod(40) +modules["DOM_MEDIA"] = Mod(41) +modules["URL_CLASSIFIER"] = Mod(42) +# ErrorResult gets its own module to reduce the chance of someone accidentally +# defining an error code matching one of the ErrorResult ones. +modules["ERRORRESULT"] = Mod(43) +# Win32 system error codes, which are not mapped to a specific other value, +# see Bug 1686041. +modules["WIN32"] = Mod(44) +modules["WDBA"] = Mod(45) + +# 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. +modules["GENERAL"] = Mod(51) + +MODULE_BASE_OFFSET = 0x45 + +NS_ERROR_SEVERITY_SUCCESS = 0 +NS_ERROR_SEVERITY_ERROR = 1 + + +def SUCCESS_OR_FAILURE(sev, module, code): + return (sev << 31) | ((module + MODULE_BASE_OFFSET) << 16) | code + + +def FAILURE(code): + return SUCCESS_OR_FAILURE(NS_ERROR_SEVERITY_ERROR, Mod.active.num, code) + + +def SUCCESS(code): + return SUCCESS_OR_FAILURE(NS_ERROR_SEVERITY_SUCCESS, Mod.active.num, code) + + +# Errors is an ordered dictionary, so that we can recover the order in which +# they were defined. This is important for determining which name is the +# canonical name for an error code. +errors = OrderedDict() + +# Standard "it worked" return value +errors["NS_OK"] = 0 + +# ======================================================================= +# Core errors, not part of any modules +# ======================================================================= +errors["NS_ERROR_BASE"] = 0xC1F30000 +# Returned when an instance is not initialized +errors["NS_ERROR_NOT_INITIALIZED"] = errors["NS_ERROR_BASE"] + 1 +# Returned when an instance is already initialized +errors["NS_ERROR_ALREADY_INITIALIZED"] = errors["NS_ERROR_BASE"] + 2 +# Returned by a not implemented function +errors["NS_ERROR_NOT_IMPLEMENTED"] = 0x80004001 +# Returned when a given interface is not supported. +errors["NS_NOINTERFACE"] = 0x80004002 +errors["NS_ERROR_NO_INTERFACE"] = errors["NS_NOINTERFACE"] +# Returned when a function aborts +errors["NS_ERROR_ABORT"] = 0x80004004 +# Returned when a function fails +errors["NS_ERROR_FAILURE"] = 0x80004005 +# Returned when an unexpected error occurs +errors["NS_ERROR_UNEXPECTED"] = 0x8000FFFF +# Returned when a memory allocation fails +errors["NS_ERROR_OUT_OF_MEMORY"] = 0x8007000E +# Returned when an illegal value is passed +errors["NS_ERROR_ILLEGAL_VALUE"] = 0x80070057 +errors["NS_ERROR_INVALID_ARG"] = errors["NS_ERROR_ILLEGAL_VALUE"] +errors["NS_ERROR_INVALID_POINTER"] = errors["NS_ERROR_INVALID_ARG"] +errors["NS_ERROR_NULL_POINTER"] = errors["NS_ERROR_INVALID_ARG"] +# Returned when an operation can't complete due to an unavailable resource +errors["NS_ERROR_NOT_AVAILABLE"] = 0x80040111 +# Returned when a class is not registered +errors["NS_ERROR_FACTORY_NOT_REGISTERED"] = 0x80040154 +# Returned when a class cannot be registered, but may be tried again later +errors["NS_ERROR_FACTORY_REGISTER_AGAIN"] = 0x80040155 +# Returned when a dynamically loaded factory couldn't be found +errors["NS_ERROR_FACTORY_NOT_LOADED"] = 0x800401F8 +# Returned when a factory doesn't support signatures +errors["NS_ERROR_FACTORY_NO_SIGNATURE_SUPPORT"] = errors["NS_ERROR_BASE"] + 0x101 +# Returned when a factory already is registered +errors["NS_ERROR_FACTORY_EXISTS"] = errors["NS_ERROR_BASE"] + 0x100 + + +# ======================================================================= +# 1: NS_ERROR_MODULE_XPCOM +# ======================================================================= +with modules["XPCOM"]: + # Result codes used by nsIVariant + errors["NS_ERROR_CANNOT_CONVERT_DATA"] = FAILURE(1) + errors["NS_ERROR_OBJECT_IS_IMMUTABLE"] = FAILURE(2) + errors["NS_ERROR_LOSS_OF_SIGNIFICANT_DATA"] = FAILURE(3) + # Result code used by nsIThreadManager + errors["NS_ERROR_NOT_SAME_THREAD"] = FAILURE(4) + # Various operations are not permitted during XPCOM shutdown and will fail + # with this exception. + errors["NS_ERROR_ILLEGAL_DURING_SHUTDOWN"] = FAILURE(30) + errors["NS_ERROR_SERVICE_NOT_AVAILABLE"] = FAILURE(22) + + errors["NS_SUCCESS_LOSS_OF_INSIGNIFICANT_DATA"] = SUCCESS(1) + # Used by nsCycleCollectionParticipant + errors["NS_SUCCESS_INTERRUPTED_TRAVERSE"] = SUCCESS(2) + +# ======================================================================= +# 2: NS_ERROR_MODULE_BASE +# ======================================================================= +with modules["BASE"]: + # I/O Errors + + # Stream closed + errors["NS_BASE_STREAM_CLOSED"] = FAILURE(2) + # Error from the operating system + errors["NS_BASE_STREAM_OSERROR"] = FAILURE(3) + # Illegal arguments + errors["NS_BASE_STREAM_ILLEGAL_ARGS"] = FAILURE(4) + # For unichar streams + errors["NS_BASE_STREAM_NO_CONVERTER"] = FAILURE(5) + # For unichar streams + errors["NS_BASE_STREAM_BAD_CONVERSION"] = FAILURE(6) + errors["NS_BASE_STREAM_WOULD_BLOCK"] = FAILURE(7) + + +# ======================================================================= +# 3: NS_ERROR_MODULE_GFX +# ======================================================================= +with modules["GFX"]: + # no printer available (e.g. cannot find _any_ printer) + errors["NS_ERROR_GFX_PRINTER_NO_PRINTER_AVAILABLE"] = FAILURE(1) + # _specified_ (by name) printer not found + errors["NS_ERROR_GFX_PRINTER_NAME_NOT_FOUND"] = FAILURE(2) + # print-to-file: could not open output file + errors["NS_ERROR_GFX_PRINTER_COULD_NOT_OPEN_FILE"] = FAILURE(3) + # print: starting document + errors["NS_ERROR_GFX_PRINTER_STARTDOC"] = FAILURE(4) + # print: ending document + errors["NS_ERROR_GFX_PRINTER_ENDDOC"] = FAILURE(5) + # print: starting page + errors["NS_ERROR_GFX_PRINTER_STARTPAGE"] = FAILURE(6) + # The document is still being loaded + errors["NS_ERROR_GFX_PRINTER_DOC_IS_BUSY"] = FAILURE(7) + + # Font cmap is strangely structured - avoid this font! + errors["NS_ERROR_GFX_CMAP_MALFORMED"] = FAILURE(51) + + +# ======================================================================= +# 4: NS_ERROR_MODULE_WIDGET +# ======================================================================= +with modules["WIDGET"]: + # Used by: + # - nsIWidget::NotifyIME() + # Returned when the notification or the event is handled and it's consumed + # by somebody. + errors["NS_SUCCESS_EVENT_CONSUMED"] = SUCCESS(1) + + +# ======================================================================= +# 6: NS_ERROR_MODULE_NETWORK +# ======================================================================= +with modules["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. + errors["NS_BINDING_SUCCEEDED"] = errors["NS_OK"] + + # The async request failed for some unknown reason. + errors["NS_BINDING_FAILED"] = FAILURE(1) + # The async request failed because it was aborted by some user action. + errors["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. + errors["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. + errors["NS_BINDING_RETARGETED"] = FAILURE(4) + + # Miscellaneous error codes: These errors are not typically passed via + # onStopRequest. + + # The URI is malformed. + errors["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). + errors["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. + errors["NS_ERROR_NO_CONTENT"] = FAILURE(17) + # The URI scheme corresponds to an unknown protocol handler. + errors["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 + errors["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. + errors["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. + errors["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 + errors["NS_ERROR_FIRST_HEADER_FIELD_COMPONENT_EMPTY"] = FAILURE(34) + # Returned from nsIChannel::asyncOpen when trying to open the channel again + # (reopening is not supported). + errors["NS_ERROR_ALREADY_OPENED"] = FAILURE(73) + + # Connectivity error codes: + + # The connection is already established. XXX unused - consider removing. + errors["NS_ERROR_ALREADY_CONNECTED"] = FAILURE(11) + # The connection does not exist. XXX unused - consider removing. + errors["NS_ERROR_NOT_CONNECTED"] = FAILURE(12) + # The connection attempt failed, for example, because no server was + # listening at specified host:port. + errors["NS_ERROR_CONNECTION_REFUSED"] = FAILURE(13) + # The connection was lost due to a timeout error. + errors["NS_ERROR_NET_TIMEOUT"] = FAILURE(14) + # The requested action could not be completed while the networking library + # is in the offline state. + errors["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. + errors["NS_ERROR_PORT_ACCESS_NOT_ALLOWED"] = FAILURE(19) + # The connection was established, but no data was ever received. + errors["NS_ERROR_NET_RESET"] = FAILURE(20) + # The connection was established, but the data transfer was interrupted. + errors["NS_ERROR_NET_INTERRUPT"] = FAILURE(71) + # The connection attempt to a proxy failed. + errors["NS_ERROR_PROXY_CONNECTION_REFUSED"] = FAILURE(72) + # A transfer was only partially done when it completed. + errors["NS_ERROR_NET_PARTIAL_TRANSFER"] = FAILURE(76) + # HTTP/2 detected invalid TLS configuration + errors["NS_ERROR_NET_INADEQUATE_SECURITY"] = FAILURE(82) + # HTTP/2 sent a GOAWAY + errors["NS_ERROR_NET_HTTP2_SENT_GOAWAY"] = FAILURE(83) + # HTTP/3 protocol internal error + errors["NS_ERROR_NET_HTTP3_PROTOCOL_ERROR"] = FAILURE(84) + # A timeout error code that can be used to cancel requests. + errors["NS_ERROR_NET_TIMEOUT_EXTERNAL"] = FAILURE(85) + # An error related to HTTPS-only mode + errors["NS_ERROR_HTTPS_ONLY"] = FAILURE(86) + # A WebSocket connection is failed. + errors["NS_ERROR_WEBSOCKET_CONNECTION_REFUSED"] = FAILURE(87) + # A connection to a non local address is refused because + # xpc::AreNonLocalConnectionsDisabled() returns true. + errors["NS_ERROR_NON_LOCAL_CONNECTION_REFUSED"] = FAILURE(88) + # Connection to a sts host without a hsts header. + errors["NS_ERROR_BAD_HSTS_CERT"] = FAILURE(89) + # Error parsing the status line of an HTTP response + errors["NS_ERROR_PARSING_HTTP_STATUS_LINE"] = FAILURE(90) + # The user refused to navigate to a potentially unsafe URL with + # embedded credentials/superfluos authentication. + errors["NS_ERROR_SUPERFLUOS_AUTH"] = FAILURE(91) + + # 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. + errors["NS_ERROR_NOT_RESUMABLE"] = FAILURE(25) + # The request failed as a result of a detected redirection loop. + errors["NS_ERROR_REDIRECT_LOOP"] = FAILURE(31) + # It was attempted to resume the request, but the entity has changed in the + # meantime. + errors["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). + errors["NS_ERROR_UNSAFE_CONTENT_TYPE"] = FAILURE(74) + # The request resulted in an error page being displayed. + errors["NS_ERROR_LOAD_SHOWED_ERRORPAGE"] = FAILURE(77) + # The request occurred in docshell that lacks a treeowner, so it is + # probably in the process of being torn down. + errors["NS_ERROR_DOCSHELL_DYING"] = FAILURE(78) + + # DNS specific error codes: + + # The lookup of a hostname failed. This generally refers to the hostname + # from the URL being loaded. + errors["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 + errors["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. + errors["NS_ERROR_UNKNOWN_PROXY_HOST"] = FAILURE(42) + # This DNS error will occur when the resolver uses the Extended DNS Error + # option to indicate an error code for which we should not fall back to the + # default DNS resolver. This means the DNS failure is definitive. + errors["NS_ERROR_DEFINITIVE_UNKNOWN_HOST"] = FAILURE(43) + + # Socket specific error codes: + + # The specified socket type does not exist. + errors["NS_ERROR_UNKNOWN_SOCKET_TYPE"] = FAILURE(51) + # The specified socket type could not be created. + errors["NS_ERROR_SOCKET_CREATE_FAILED"] = FAILURE(52) + # The operating system doesn't support the given type of address. + errors["NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED"] = FAILURE(53) + # The address to which we tried to bind the socket was busy. + errors["NS_ERROR_SOCKET_ADDRESS_IN_USE"] = FAILURE(54) + + # Cache specific error codes: + errors["NS_ERROR_CACHE_KEY_NOT_FOUND"] = FAILURE(61) + errors["NS_ERROR_CACHE_DATA_IS_STREAM"] = FAILURE(62) + errors["NS_ERROR_CACHE_DATA_IS_NOT_STREAM"] = FAILURE(63) + errors["NS_ERROR_CACHE_WAIT_FOR_VALIDATION"] = FAILURE(64) + errors["NS_ERROR_CACHE_ENTRY_DOOMED"] = FAILURE(65) + errors["NS_ERROR_CACHE_READ_ACCESS_DENIED"] = FAILURE(66) + errors["NS_ERROR_CACHE_WRITE_ACCESS_DENIED"] = FAILURE(67) + errors["NS_ERROR_CACHE_IN_USE"] = FAILURE(68) + # Error passed through onStopRequest if the document could not be fetched + # from the cache. + errors["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. + errors["NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS"] = FAILURE(80) + # The host string is an IP address. + errors["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. + errors["NS_SUCCESS_ADOPTED_DATA"] = SUCCESS(90) + + # 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. + errors["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 + errors["NS_NET_STATUS_READING"] = SUCCESS(8) + errors["NS_NET_STATUS_WRITING"] = SUCCESS(9) + + # nsISocketTransport + errors["NS_NET_STATUS_RESOLVING_HOST"] = SUCCESS(3) + errors["NS_NET_STATUS_RESOLVED_HOST"] = SUCCESS(11) + errors["NS_NET_STATUS_CONNECTING_TO"] = SUCCESS(7) + errors["NS_NET_STATUS_CONNECTED_TO"] = SUCCESS(4) + errors["NS_NET_STATUS_TLS_HANDSHAKE_STARTING"] = SUCCESS(12) + errors["NS_NET_STATUS_TLS_HANDSHAKE_ENDED"] = SUCCESS(13) + errors["NS_NET_STATUS_SENDING_TO"] = SUCCESS(5) + errors["NS_NET_STATUS_WAITING_FOR"] = SUCCESS(10) + errors["NS_NET_STATUS_RECEIVING_FROM"] = SUCCESS(6) + + # nsIInterceptedChannel + # Generic error for non-specific failures during service worker interception + errors["NS_ERROR_INTERCEPTION_FAILED"] = FAILURE(100) + + errors["NS_ERROR_WEBTRANSPORT_CODE_BASE"] = FAILURE(200) + errors["NS_ERROR_WEBTRANSPORT_CODE_END"] = ( + errors["NS_ERROR_WEBTRANSPORT_CODE_BASE"] + 255 + ) + + # All Http proxy CONNECT response codes + errors["NS_ERROR_PROXY_CODE_BASE"] = FAILURE(1000) + # Redirection 3xx + errors["NS_ERROR_PROXY_MULTIPLE_CHOICES"] = errors["NS_ERROR_PROXY_CODE_BASE"] + 300 + errors["NS_ERROR_PROXY_MOVED_PERMANENTLY"] = ( + errors["NS_ERROR_PROXY_CODE_BASE"] + 301 + ) + errors["NS_ERROR_PROXY_FOUND"] = errors["NS_ERROR_PROXY_CODE_BASE"] + 302 + errors["NS_ERROR_PROXY_SEE_OTHER"] = errors["NS_ERROR_PROXY_CODE_BASE"] + 303 + errors["NS_ERROR_PROXY_NOT_MODIFIED"] = errors["NS_ERROR_PROXY_CODE_BASE"] + 304 + errors["NS_ERROR_PROXY_TEMPORARY_REDIRECT"] = ( + errors["NS_ERROR_PROXY_CODE_BASE"] + 307 + ) + errors["NS_ERROR_PROXY_PERMANENT_REDIRECT"] = ( + errors["NS_ERROR_PROXY_CODE_BASE"] + 308 + ) + + # Client error 4xx + errors["NS_ERROR_PROXY_BAD_REQUEST"] = errors["NS_ERROR_PROXY_CODE_BASE"] + 400 + errors["NS_ERROR_PROXY_UNAUTHORIZED"] = errors["NS_ERROR_PROXY_CODE_BASE"] + 401 + errors["NS_ERROR_PROXY_PAYMENT_REQUIRED"] = errors["NS_ERROR_PROXY_CODE_BASE"] + 402 + errors["NS_ERROR_PROXY_FORBIDDEN"] = errors["NS_ERROR_PROXY_CODE_BASE"] + 403 + errors["NS_ERROR_PROXY_NOT_FOUND"] = errors["NS_ERROR_PROXY_CODE_BASE"] + 404 + errors["NS_ERROR_PROXY_METHOD_NOT_ALLOWED"] = ( + errors["NS_ERROR_PROXY_CODE_BASE"] + 405 + ) + errors["NS_ERROR_PROXY_NOT_ACCEPTABLE"] = errors["NS_ERROR_PROXY_CODE_BASE"] + 406 + # The proxy requires authentication; used when we can't easily propagate 407s. + errors["NS_ERROR_PROXY_AUTHENTICATION_FAILED"] = ( + errors["NS_ERROR_PROXY_CODE_BASE"] + 407 + ) + errors["NS_ERROR_PROXY_REQUEST_TIMEOUT"] = errors["NS_ERROR_PROXY_CODE_BASE"] + 408 + errors["NS_ERROR_PROXY_CONFLICT"] = errors["NS_ERROR_PROXY_CODE_BASE"] + 409 + errors["NS_ERROR_PROXY_GONE"] = errors["NS_ERROR_PROXY_CODE_BASE"] + 410 + errors["NS_ERROR_PROXY_LENGTH_REQUIRED"] = errors["NS_ERROR_PROXY_CODE_BASE"] + 411 + errors["NS_ERROR_PROXY_PRECONDITION_FAILED"] = ( + errors["NS_ERROR_PROXY_CODE_BASE"] + 412 + ) + errors["NS_ERROR_PROXY_REQUEST_ENTITY_TOO_LARGE"] = ( + errors["NS_ERROR_PROXY_CODE_BASE"] + 413 + ) + errors["NS_ERROR_PROXY_REQUEST_URI_TOO_LONG"] = ( + errors["NS_ERROR_PROXY_CODE_BASE"] + 414 + ) + errors["NS_ERROR_PROXY_UNSUPPORTED_MEDIA_TYPE"] = ( + errors["NS_ERROR_PROXY_CODE_BASE"] + 415 + ) + errors["NS_ERROR_PROXY_REQUESTED_RANGE_NOT_SATISFIABLE"] = ( + errors["NS_ERROR_PROXY_CODE_BASE"] + 416 + ) + errors["NS_ERROR_PROXY_EXPECTATION_FAILED"] = ( + errors["NS_ERROR_PROXY_CODE_BASE"] + 417 + ) + errors["NS_ERROR_PROXY_MISDIRECTED_REQUEST"] = ( + errors["NS_ERROR_PROXY_CODE_BASE"] + 421 + ) + errors["NS_ERROR_PROXY_TOO_EARLY"] = errors["NS_ERROR_PROXY_CODE_BASE"] + 425 + errors["NS_ERROR_PROXY_UPGRADE_REQUIRED"] = errors["NS_ERROR_PROXY_CODE_BASE"] + 426 + errors["NS_ERROR_PROXY_PRECONDITION_REQUIRED"] = ( + errors["NS_ERROR_PROXY_CODE_BASE"] + 428 + ) + # Indicates that we have sent too many requests in a given amount of time. + errors["NS_ERROR_PROXY_TOO_MANY_REQUESTS"] = ( + errors["NS_ERROR_PROXY_CODE_BASE"] + 429 + ) + errors["NS_ERROR_PROXY_REQUEST_HEADER_FIELDS_TOO_LARGE"] = ( + errors["NS_ERROR_PROXY_CODE_BASE"] + 431 + ) + errors["NS_ERROR_PROXY_UNAVAILABLE_FOR_LEGAL_REASONS"] = ( + errors["NS_ERROR_PROXY_CODE_BASE"] + 451 + ) + + # Server error 5xx + errors["NS_ERROR_PROXY_INTERNAL_SERVER_ERROR"] = ( + errors["NS_ERROR_PROXY_CODE_BASE"] + 500 + ) + errors["NS_ERROR_PROXY_NOT_IMPLEMENTED"] = errors["NS_ERROR_PROXY_CODE_BASE"] + 501 + errors["NS_ERROR_PROXY_BAD_GATEWAY"] = errors["NS_ERROR_PROXY_CODE_BASE"] + 502 + errors["NS_ERROR_PROXY_SERVICE_UNAVAILABLE"] = ( + errors["NS_ERROR_PROXY_CODE_BASE"] + 503 + ) + # The proxy did get any response from the remote server in time. + errors["NS_ERROR_PROXY_GATEWAY_TIMEOUT"] = errors["NS_ERROR_PROXY_CODE_BASE"] + 504 + errors["NS_ERROR_PROXY_VERSION_NOT_SUPPORTED"] = ( + errors["NS_ERROR_PROXY_CODE_BASE"] + 505 + ) + errors["NS_ERROR_PROXY_VARIANT_ALSO_NEGOTIATES"] = ( + errors["NS_ERROR_PROXY_CODE_BASE"] + 506 + ) + errors["NS_ERROR_PROXY_NOT_EXTENDED"] = errors["NS_ERROR_PROXY_CODE_BASE"] + 510 + errors["NS_ERROR_PROXY_NETWORK_AUTHENTICATION_REQUIRED"] = ( + errors["NS_ERROR_PROXY_CODE_BASE"] + 511 + ) + +# ======================================================================= +# 7: NS_ERROR_MODULE_PLUGINS +# ======================================================================= +with modules["PLUGINS"]: + errors["NS_ERROR_PLUGINS_PLUGINSNOTCHANGED"] = FAILURE(1000) + errors["NS_ERROR_PLUGIN_DISABLED"] = FAILURE(1001) + errors["NS_ERROR_PLUGIN_BLOCKLISTED"] = FAILURE(1002) + errors["NS_ERROR_PLUGIN_TIME_RANGE_NOT_SUPPORTED"] = FAILURE(1003) + errors["NS_ERROR_PLUGIN_CLICKTOPLAY"] = FAILURE(1004) + + +# ======================================================================= +# 8: NS_ERROR_MODULE_LAYOUT +# ======================================================================= +with modules["LAYOUT"]: + # Return code for SheetLoadData::VerifySheetReadyToParse + errors["NS_OK_PARSE_SHEET"] = SUCCESS(1) + + +# ======================================================================= +# 9: NS_ERROR_MODULE_HTMLPARSER +# ======================================================================= +with modules["HTMLPARSER"]: + errors["NS_ERROR_HTMLPARSER_CONTINUE"] = errors["NS_OK"] + + errors["NS_ERROR_HTMLPARSER_EOF"] = FAILURE(1000) + errors["NS_ERROR_HTMLPARSER_UNKNOWN"] = FAILURE(1001) + errors["NS_ERROR_HTMLPARSER_CANTPROPAGATE"] = FAILURE(1002) + errors["NS_ERROR_HTMLPARSER_CONTEXTMISMATCH"] = FAILURE(1003) + errors["NS_ERROR_HTMLPARSER_BADFILENAME"] = FAILURE(1004) + errors["NS_ERROR_HTMLPARSER_BADURL"] = FAILURE(1005) + errors["NS_ERROR_HTMLPARSER_INVALIDPARSERCONTEXT"] = FAILURE(1006) + errors["NS_ERROR_HTMLPARSER_INTERRUPTED"] = FAILURE(1007) + errors["NS_ERROR_HTMLPARSER_BLOCK"] = FAILURE(1008) + errors["NS_ERROR_HTMLPARSER_BADTOKENIZER"] = FAILURE(1009) + errors["NS_ERROR_HTMLPARSER_BADATTRIBUTE"] = FAILURE(1010) + errors["NS_ERROR_HTMLPARSER_UNRESOLVEDDTD"] = FAILURE(1011) + errors["NS_ERROR_HTMLPARSER_MISPLACEDTABLECONTENT"] = FAILURE(1012) + errors["NS_ERROR_HTMLPARSER_BADDTD"] = FAILURE(1013) + errors["NS_ERROR_HTMLPARSER_BADCONTEXT"] = FAILURE(1014) + errors["NS_ERROR_HTMLPARSER_STOPPARSING"] = FAILURE(1015) + errors["NS_ERROR_HTMLPARSER_UNTERMINATEDSTRINGLITERAL"] = FAILURE(1016) + errors["NS_ERROR_HTMLPARSER_HIERARCHYTOODEEP"] = FAILURE(1017) + errors["NS_ERROR_HTMLPARSER_FAKE_ENDTAG"] = FAILURE(1018) + errors["NS_ERROR_HTMLPARSER_INVALID_COMMENT"] = FAILURE(1019) + + +# ======================================================================= +# 10: NS_ERROR_MODULE_RDF +# ======================================================================= +with modules["RDF"]: + # Returned from nsIRDFDataSource::Assert() and Unassert() if the assertion + # (or unassertion was accepted by the datasource + errors["NS_RDF_ASSERTION_ACCEPTED"] = errors["NS_OK"] + # Returned from nsIRDFDataSource::GetSource() and GetTarget() if the + # source/target has no value + errors["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. + errors["NS_RDF_ASSERTION_REJECTED"] = SUCCESS(3) + # Return this from rdfITripleVisitor to stop cycling + errors["NS_RDF_STOP_VISIT"] = SUCCESS(4) + + +# ======================================================================= +# 11: NS_ERROR_MODULE_UCONV +# ======================================================================= +with modules["UCONV"]: + errors["NS_ERROR_UCONV_NOCONV"] = FAILURE(1) + errors["NS_ERROR_UDEC_ILLEGALINPUT"] = FAILURE(14) + + errors["NS_OK_HAD_REPLACEMENTS"] = SUCCESS(3) + errors["NS_OK_UDEC_MOREINPUT"] = SUCCESS(12) + errors["NS_OK_UDEC_MOREOUTPUT"] = SUCCESS(13) + errors["NS_OK_UENC_MOREOUTPUT"] = SUCCESS(34) + errors["NS_ERROR_UENC_NOMAPPING"] = SUCCESS(35) + + # BEGIN DEPRECATED + errors["NS_ERROR_ILLEGAL_INPUT"] = errors["NS_ERROR_UDEC_ILLEGALINPUT"] + # END DEPRECATED + + +# ======================================================================= +# 13: NS_ERROR_MODULE_FILES +# ======================================================================= +with modules["FILES"]: + errors["NS_ERROR_FILE_UNRECOGNIZED_PATH"] = FAILURE(1) + errors["NS_ERROR_FILE_UNRESOLVABLE_SYMLINK"] = FAILURE(2) + errors["NS_ERROR_FILE_EXECUTION_FAILED"] = FAILURE(3) + errors["NS_ERROR_FILE_UNKNOWN_TYPE"] = FAILURE(4) + errors["NS_ERROR_FILE_DESTINATION_NOT_DIR"] = FAILURE(5) + errors["NS_ERROR_FILE_COPY_OR_MOVE_FAILED"] = FAILURE(7) + errors["NS_ERROR_FILE_ALREADY_EXISTS"] = FAILURE(8) + errors["NS_ERROR_FILE_INVALID_PATH"] = FAILURE(9) + errors["NS_ERROR_FILE_CORRUPTED"] = FAILURE(11) + errors["NS_ERROR_FILE_NOT_DIRECTORY"] = FAILURE(12) + errors["NS_ERROR_FILE_IS_DIRECTORY"] = FAILURE(13) + errors["NS_ERROR_FILE_IS_LOCKED"] = FAILURE(14) + errors["NS_ERROR_FILE_TOO_BIG"] = FAILURE(15) + errors["NS_ERROR_FILE_NO_DEVICE_SPACE"] = FAILURE(16) + errors["NS_ERROR_FILE_NAME_TOO_LONG"] = FAILURE(17) + errors["NS_ERROR_FILE_NOT_FOUND"] = FAILURE(18) + errors["NS_ERROR_FILE_READ_ONLY"] = FAILURE(19) + errors["NS_ERROR_FILE_DIR_NOT_EMPTY"] = FAILURE(20) + errors["NS_ERROR_FILE_ACCESS_DENIED"] = FAILURE(21) + errors["NS_ERROR_FILE_FS_CORRUPTED"] = FAILURE(22) + errors["NS_ERROR_FILE_DEVICE_FAILURE"] = FAILURE(23) + errors["NS_ERROR_FILE_DEVICE_TEMPORARY_FAILURE"] = FAILURE(24) + errors["NS_ERROR_FILE_INVALID_HANDLE"] = FAILURE(25) + + errors["NS_SUCCESS_FILE_DIRECTORY_EMPTY"] = SUCCESS(1) + # Result codes used by nsIDirectoryServiceProvider2 + errors["NS_SUCCESS_AGGREGATE_RESULT"] = SUCCESS(2) + + +# ======================================================================= +# 14: NS_ERROR_MODULE_DOM +# ======================================================================= +with modules["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 + errors["NS_ERROR_DOM_INDEX_SIZE_ERR"] = FAILURE(1) + errors["NS_ERROR_DOM_HIERARCHY_REQUEST_ERR"] = FAILURE(3) + errors["NS_ERROR_DOM_WRONG_DOCUMENT_ERR"] = FAILURE(4) + errors["NS_ERROR_DOM_INVALID_CHARACTER_ERR"] = FAILURE(5) + errors["NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR"] = FAILURE(7) + errors["NS_ERROR_DOM_NOT_FOUND_ERR"] = FAILURE(8) + errors["NS_ERROR_DOM_NOT_SUPPORTED_ERR"] = FAILURE(9) + errors["NS_ERROR_DOM_INUSE_ATTRIBUTE_ERR"] = FAILURE(10) + errors["NS_ERROR_DOM_INVALID_STATE_ERR"] = FAILURE(11) + errors["NS_ERROR_DOM_SYNTAX_ERR"] = FAILURE(12) + errors["NS_ERROR_DOM_INVALID_MODIFICATION_ERR"] = FAILURE(13) + errors["NS_ERROR_DOM_NAMESPACE_ERR"] = FAILURE(14) + errors["NS_ERROR_DOM_INVALID_ACCESS_ERR"] = FAILURE(15) + errors["NS_ERROR_DOM_TYPE_MISMATCH_ERR"] = FAILURE(17) + errors["NS_ERROR_DOM_SECURITY_ERR"] = FAILURE(18) + errors["NS_ERROR_DOM_NETWORK_ERR"] = FAILURE(19) + errors["NS_ERROR_DOM_ABORT_ERR"] = FAILURE(20) + errors["NS_ERROR_DOM_URL_MISMATCH_ERR"] = FAILURE(21) + errors["NS_ERROR_DOM_QUOTA_EXCEEDED_ERR"] = FAILURE(22) + errors["NS_ERROR_DOM_TIMEOUT_ERR"] = FAILURE(23) + errors["NS_ERROR_DOM_INVALID_NODE_TYPE_ERR"] = FAILURE(24) + errors["NS_ERROR_DOM_DATA_CLONE_ERR"] = FAILURE(25) + # StringEncoding API errors from http://wiki.whatwg.org/wiki/StringEncoding + errors["NS_ERROR_DOM_ENCODING_NOT_SUPPORTED_ERR"] = FAILURE(28) + # WebCrypto API errors from http://www.w3.org/TR/WebCryptoAPI/ + errors["NS_ERROR_DOM_UNKNOWN_ERR"] = FAILURE(30) + errors["NS_ERROR_DOM_DATA_ERR"] = FAILURE(31) + errors["NS_ERROR_DOM_OPERATION_ERR"] = FAILURE(32) + # https://heycam.github.io/webidl/#notallowederror + errors["NS_ERROR_DOM_NOT_ALLOWED_ERR"] = FAILURE(33) + # DOM error codes defined by us + errors["NS_ERROR_DOM_WRONG_TYPE_ERR"] = FAILURE(1002) + errors["NS_ERROR_DOM_NOT_NUMBER_ERR"] = FAILURE(1005) + errors["NS_ERROR_DOM_PROP_ACCESS_DENIED"] = FAILURE(1010) + errors["NS_ERROR_DOM_XPCONNECT_ACCESS_DENIED"] = FAILURE(1011) + errors["NS_ERROR_DOM_BAD_URI"] = FAILURE(1012) + errors["NS_ERROR_DOM_RETVAL_UNDEFINED"] = FAILURE(1013) + + # A way to represent uncatchable exceptions + errors["NS_ERROR_UNCATCHABLE_EXCEPTION"] = FAILURE(1015) + + errors["NS_ERROR_DOM_MALFORMED_URI"] = FAILURE(1016) + errors["NS_ERROR_DOM_INVALID_HEADER_NAME"] = FAILURE(1017) + + errors["NS_ERROR_DOM_INVALID_STATE_XHR_HAS_INVALID_CONTEXT"] = FAILURE(1018) + + # When manipulating the bytecode cache with the JS API, some transcoding + # errors, such as a different bytecode format can cause failures of the + # decoding process. + errors["NS_ERROR_DOM_JS_DECODING_ERROR"] = FAILURE(1026) + + # Image decode errors. + errors["NS_ERROR_DOM_IMAGE_INACTIVE_DOCUMENT"] = FAILURE(1027) + errors["NS_ERROR_DOM_IMAGE_INVALID_REQUEST"] = FAILURE(1028) + errors["NS_ERROR_DOM_IMAGE_BROKEN"] = FAILURE(1029) + + # Used to indicate that a resource with the Cross-Origin-Resource-Policy + # response header set failed the origin check. + # https://fetch.spec.whatwg.org/#cross-origin-resource-policy-header + errors["NS_ERROR_DOM_CORP_FAILED"] = FAILURE(1036) + + # Used to indicate that a URI may not be loaded into a cross-origin + # context. + errors["NS_ERROR_DOM_BAD_CROSS_ORIGIN_URI"] = FAILURE(1037) + + # The request failed because there are too many recursive iframes or + # objects being loaded. + errors["NS_ERROR_RECURSIVE_DOCUMENT_LOAD"] = FAILURE(1038) + + # WebExtension content script may not load this URL. + errors["NS_ERROR_DOM_WEBEXT_CONTENT_SCRIPT_URI"] = FAILURE(1039) + + # Used to indicate that a resource load was blocked because of the + # Cross-Origin-Embedder-Policy response header. + # https://html.spec.whatwg.org/multipage/origin.html#coep + errors["NS_ERROR_DOM_COEP_FAILED"] = FAILURE(1040) + + # Used to indicate that a resource load was blocked because of the + # Cross-Origin-Opener-Policy response header. + # https://html.spec.whatwg.org/multipage/origin.html#cross-origin-opener-policies + errors["NS_ERROR_DOM_COOP_FAILED"] = FAILURE(1041) + + errors["NS_ERROR_DOM_INVALID_HEADER_VALUE"] = FAILURE(1042) + + # 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. + errors["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. + errors["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. + errors["NS_SUCCESS_DOM_SCRIPT_EVALUATION_THREW_UNCATCHABLE"] = SUCCESS(3) + + +# ======================================================================= +# 15: NS_ERROR_MODULE_IMGLIB +# ======================================================================= +with modules["IMGLIB"]: + errors["NS_IMAGELIB_ERROR_FAILURE"] = FAILURE(5) + errors["NS_IMAGELIB_ERROR_NO_DECODER"] = FAILURE(6) + errors["NS_IMAGELIB_ERROR_NOT_FINISHED"] = FAILURE(7) + errors["NS_IMAGELIB_ERROR_NO_ENCODER"] = FAILURE(9) + + +# ======================================================================= +# 17: NS_ERROR_MODULE_EDITOR +# ======================================================================= +with modules["EDITOR"]: + errors["NS_ERROR_EDITOR_DESTROYED"] = FAILURE(1) + + # An error code that indicates that the DOM tree has been modified by + # web app or add-on while the editor modifying the tree. However, + # this shouldn't be exposed to the web because the result should've + # been expected by the web app. + errors["NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE"] = FAILURE(2) + + # An error code that indicates that the edit action canceled by + # clipboard event listener or beforeinput event listener. Note that + # don't make this as a success code since it's not check with NS_FAILED() + # and may keep handling the operation unexpectedly. + errors["NS_ERROR_EDITOR_ACTION_CANCELED"] = FAILURE(3) + + # An error code that indicates that there is no editable selection ranges. + # E.g., Selection has no range, caret is in non-editable element, + # non-collapsed range crosses editing host boundaries. + errors["NS_ERROR_EDITOR_NO_EDITABLE_RANGE"] = FAILURE(4) + + errors["NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND"] = SUCCESS(1) + errors["NS_SUCCESS_EDITOR_FOUND_TARGET"] = SUCCESS(2) + + # If most callers ignore error except serious error (like + # NS_ERROR_EDITOR_DESTROYED), this success code is useful. E.g. such + # callers can do: + # nsresult rv = Foo(); + # if (MOZ_UNLIKELY(NS_FAILED(rv))) { + # NS_WARNING("Foo() failed"); + # return rv; + # } + # NS_WARNING_ASSERTION( + # rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR, + # "Foo() failed, but ignored"); + errors["NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR"] = SUCCESS(3) + + +# ======================================================================= +# 18: NS_ERROR_MODULE_XPCONNECT +# ======================================================================= +with modules["XPCONNECT"]: + errors["NS_ERROR_XPC_NOT_ENOUGH_ARGS"] = FAILURE(1) + errors["NS_ERROR_XPC_NEED_OUT_OBJECT"] = FAILURE(2) + errors["NS_ERROR_XPC_CANT_SET_OUT_VAL"] = FAILURE(3) + errors["NS_ERROR_XPC_NATIVE_RETURNED_FAILURE"] = FAILURE(4) + errors["NS_ERROR_XPC_CANT_GET_INTERFACE_INFO"] = FAILURE(5) + errors["NS_ERROR_XPC_CANT_GET_PARAM_IFACE_INFO"] = FAILURE(6) + errors["NS_ERROR_XPC_CANT_GET_METHOD_INFO"] = FAILURE(7) + errors["NS_ERROR_XPC_UNEXPECTED"] = FAILURE(8) + errors["NS_ERROR_XPC_BAD_CONVERT_JS"] = FAILURE(9) + errors["NS_ERROR_XPC_BAD_CONVERT_NATIVE"] = FAILURE(10) + errors["NS_ERROR_XPC_BAD_CONVERT_JS_NULL_REF"] = FAILURE(11) + errors["NS_ERROR_XPC_BAD_OP_ON_WN_PROTO"] = FAILURE(12) + errors["NS_ERROR_XPC_CANT_CONVERT_WN_TO_FUN"] = FAILURE(13) + errors["NS_ERROR_XPC_CANT_DEFINE_PROP_ON_WN"] = FAILURE(14) + errors["NS_ERROR_XPC_CANT_WATCH_WN_STATIC"] = FAILURE(15) + errors["NS_ERROR_XPC_CANT_EXPORT_WN_STATIC"] = FAILURE(16) + errors["NS_ERROR_XPC_SCRIPTABLE_CALL_FAILED"] = FAILURE(17) + errors["NS_ERROR_XPC_SCRIPTABLE_CTOR_FAILED"] = FAILURE(18) + errors["NS_ERROR_XPC_CANT_CALL_WO_SCRIPTABLE"] = FAILURE(19) + errors["NS_ERROR_XPC_CANT_CTOR_WO_SCRIPTABLE"] = FAILURE(20) + errors["NS_ERROR_XPC_CI_RETURNED_FAILURE"] = FAILURE(21) + errors["NS_ERROR_XPC_GS_RETURNED_FAILURE"] = FAILURE(22) + errors["NS_ERROR_XPC_BAD_CID"] = FAILURE(23) + errors["NS_ERROR_XPC_BAD_IID"] = FAILURE(24) + errors["NS_ERROR_XPC_CANT_CREATE_WN"] = FAILURE(25) + errors["NS_ERROR_XPC_JS_THREW_EXCEPTION"] = FAILURE(26) + errors["NS_ERROR_XPC_JS_THREW_NATIVE_OBJECT"] = FAILURE(27) + errors["NS_ERROR_XPC_JS_THREW_JS_OBJECT"] = FAILURE(28) + errors["NS_ERROR_XPC_JS_THREW_NULL"] = FAILURE(29) + errors["NS_ERROR_XPC_JS_THREW_STRING"] = FAILURE(30) + errors["NS_ERROR_XPC_JS_THREW_NUMBER"] = FAILURE(31) + errors["NS_ERROR_XPC_JAVASCRIPT_ERROR"] = FAILURE(32) + errors["NS_ERROR_XPC_JAVASCRIPT_ERROR_WITH_DETAILS"] = FAILURE(33) + errors["NS_ERROR_XPC_CANT_CONVERT_PRIMITIVE_TO_ARRAY"] = FAILURE(34) + errors["NS_ERROR_XPC_CANT_CONVERT_OBJECT_TO_ARRAY"] = FAILURE(35) + errors["NS_ERROR_XPC_NOT_ENOUGH_ELEMENTS_IN_ARRAY"] = FAILURE(36) + errors["NS_ERROR_XPC_CANT_GET_ARRAY_INFO"] = FAILURE(37) + errors["NS_ERROR_XPC_NOT_ENOUGH_CHARS_IN_STRING"] = FAILURE(38) + errors["NS_ERROR_XPC_SECURITY_MANAGER_VETO"] = FAILURE(39) + errors["NS_ERROR_XPC_INTERFACE_NOT_SCRIPTABLE"] = FAILURE(40) + errors["NS_ERROR_XPC_INTERFACE_NOT_FROM_NSISUPPORTS"] = FAILURE(41) + errors["NS_ERROR_XPC_CANT_SET_READ_ONLY_CONSTANT"] = FAILURE(43) + errors["NS_ERROR_XPC_CANT_SET_READ_ONLY_ATTRIBUTE"] = FAILURE(44) + errors["NS_ERROR_XPC_CANT_SET_READ_ONLY_METHOD"] = FAILURE(45) + errors["NS_ERROR_XPC_CANT_ADD_PROP_TO_WRAPPED_NATIVE"] = FAILURE(46) + errors["NS_ERROR_XPC_CALL_TO_SCRIPTABLE_FAILED"] = FAILURE(47) + errors["NS_ERROR_XPC_JSOBJECT_HAS_NO_FUNCTION_NAMED"] = FAILURE(48) + errors["NS_ERROR_XPC_BAD_ID_STRING"] = FAILURE(49) + errors["NS_ERROR_XPC_BAD_INITIALIZER_NAME"] = FAILURE(50) + errors["NS_ERROR_XPC_HAS_BEEN_SHUTDOWN"] = FAILURE(51) + errors["NS_ERROR_XPC_CANT_MODIFY_PROP_ON_WN"] = FAILURE(52) + errors["NS_ERROR_XPC_BAD_CONVERT_JS_ZERO_ISNOT_NULL"] = FAILURE(53) + # any new errors here should have an associated entry added in xpc.msg + + +# ======================================================================= +# 19: NS_ERROR_MODULE_PROFILE +# ======================================================================= +with modules["PROFILE"]: + errors["NS_ERROR_LAUNCHED_CHILD_PROCESS"] = FAILURE(200) + errors["NS_ERROR_SHOW_PROFILE_MANAGER"] = FAILURE(201) + errors["NS_ERROR_DATABASE_CHANGED"] = FAILURE(202) + + +# ======================================================================= +# 21: NS_ERROR_MODULE_SECURITY +# ======================================================================= +with modules["SECURITY"]: + # Error code for XFO + errors["NS_ERROR_XFO_VIOLATION"] = FAILURE(96) + + # Error code for CSP + errors["NS_ERROR_CSP_FORM_ACTION_VIOLATION"] = FAILURE(97) + errors["NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION"] = FAILURE(98) + + # Error code for Sub-Resource Integrity + errors["NS_ERROR_SRI_CORRUPT"] = FAILURE(200) + errors["NS_ERROR_SRI_NOT_ELIGIBLE"] = FAILURE(201) + errors["NS_ERROR_SRI_UNEXPECTED_HASH_TYPE"] = FAILURE(202) + errors["NS_ERROR_SRI_IMPORT"] = FAILURE(203) + + # CMS specific nsresult error codes. Note: the numbers used here correspond + # to the values in nsICMSMessageErrors.idl. + errors["NS_ERROR_CMS_VERIFY_NOT_SIGNED"] = FAILURE(1024) + errors["NS_ERROR_CMS_VERIFY_NO_CONTENT_INFO"] = FAILURE(1025) + errors["NS_ERROR_CMS_VERIFY_BAD_DIGEST"] = FAILURE(1026) + errors["NS_ERROR_CMS_VERIFY_NOCERT"] = FAILURE(1028) + errors["NS_ERROR_CMS_VERIFY_UNTRUSTED"] = FAILURE(1029) + errors["NS_ERROR_CMS_VERIFY_ERROR_UNVERIFIED"] = FAILURE(1031) + errors["NS_ERROR_CMS_VERIFY_ERROR_PROCESSING"] = FAILURE(1032) + errors["NS_ERROR_CMS_VERIFY_BAD_SIGNATURE"] = FAILURE(1033) + errors["NS_ERROR_CMS_VERIFY_DIGEST_MISMATCH"] = FAILURE(1034) + errors["NS_ERROR_CMS_VERIFY_UNKNOWN_ALGO"] = FAILURE(1035) + errors["NS_ERROR_CMS_VERIFY_UNSUPPORTED_ALGO"] = FAILURE(1036) + errors["NS_ERROR_CMS_VERIFY_MALFORMED_SIGNATURE"] = FAILURE(1037) + errors["NS_ERROR_CMS_VERIFY_HEADER_MISMATCH"] = FAILURE(1038) + errors["NS_ERROR_CMS_VERIFY_NOT_YET_ATTEMPTED"] = FAILURE(1039) + errors["NS_ERROR_CMS_VERIFY_CERT_WITHOUT_ADDRESS"] = FAILURE(1040) + errors["NS_ERROR_CMS_ENCRYPT_NO_BULK_ALG"] = FAILURE(1056) + errors["NS_ERROR_CMS_ENCRYPT_INCOMPLETE"] = FAILURE(1057) + + +# ======================================================================= +# 24: NS_ERROR_MODULE_URILOADER +# ======================================================================= +with modules["URILOADER"]: + errors["NS_ERROR_WONT_HANDLE_CONTENT"] = FAILURE(1) + # The load has been cancelled because it was found on a malware or phishing + # list. + errors["NS_ERROR_MALWARE_URI"] = FAILURE(30) + errors["NS_ERROR_PHISHING_URI"] = FAILURE(31) + errors["NS_ERROR_TRACKING_URI"] = FAILURE(34) + errors["NS_ERROR_UNWANTED_URI"] = FAILURE(35) + errors["NS_ERROR_BLOCKED_URI"] = FAILURE(37) + errors["NS_ERROR_HARMFUL_URI"] = FAILURE(38) + errors["NS_ERROR_FINGERPRINTING_URI"] = FAILURE(41) + errors["NS_ERROR_CRYPTOMINING_URI"] = FAILURE(42) + errors["NS_ERROR_SOCIALTRACKING_URI"] = FAILURE(43) + errors["NS_ERROR_EMAILTRACKING_URI"] = FAILURE(44) + # Used when "Save Link As..." doesn't see the headers quickly enough to + # choose a filename. See nsContextMenu.js. + errors["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. + errors["NS_ERROR_PARSED_DATA_CACHED"] = FAILURE(33) + + # When browser.tabs.documentchannel.parent-controlled pref and SHIP + # are enabled and a load gets cancelled due to another one + # starting, the error is NS_BINDING_CANCELLED_OLD_LOAD. + errors["NS_BINDING_CANCELLED_OLD_LOAD"] = FAILURE(39) + + +# ======================================================================= +# 25: NS_ERROR_MODULE_CONTENT +# ======================================================================= +with modules["CONTENT"]: + # Error codes for content policy blocking + errors["NS_ERROR_CONTENT_BLOCKED"] = FAILURE(6) + errors["NS_ERROR_CONTENT_BLOCKED_SHOW_ALT"] = FAILURE(7) + # Success variations of content policy blocking + errors["NS_PROPTABLE_PROP_NOT_THERE"] = FAILURE(10) + # Error code for when the content process crashed + errors["NS_ERROR_CONTENT_CRASHED"] = FAILURE(16) + # Error code for when a subframe process crashed + errors["NS_ERROR_FRAME_CRASHED"] = FAILURE(14) + # Error code for when the content process had a different buildID than the + # parent + errors["NS_ERROR_BUILDID_MISMATCH"] = FAILURE(17) + + errors["NS_PROPTABLE_PROP_OVERWRITTEN"] = SUCCESS(11) + # Error codes for FindBroadcaster in XULBroadcastManager.cpp + errors["NS_FINDBROADCASTER_NOT_FOUND"] = SUCCESS(12) + errors["NS_FINDBROADCASTER_FOUND"] = SUCCESS(13) + + +# ======================================================================= +# 27: NS_ERROR_MODULE_XSLT +# ======================================================================= +with modules["XSLT"]: + errors["NS_ERROR_XPATH_INVALID_ARG"] = errors["NS_ERROR_INVALID_ARG"] + + errors["NS_ERROR_XSLT_PARSE_FAILURE"] = FAILURE(1) + errors["NS_ERROR_XPATH_PARSE_FAILURE"] = FAILURE(2) + errors["NS_ERROR_XSLT_ALREADY_SET"] = FAILURE(3) + errors["NS_ERROR_XSLT_EXECUTION_FAILURE"] = FAILURE(4) + errors["NS_ERROR_XPATH_UNKNOWN_FUNCTION"] = FAILURE(5) + errors["NS_ERROR_XSLT_BAD_RECURSION"] = FAILURE(6) + errors["NS_ERROR_XSLT_BAD_VALUE"] = FAILURE(7) + errors["NS_ERROR_XSLT_NODESET_EXPECTED"] = FAILURE(8) + errors["NS_ERROR_XSLT_ABORTED"] = FAILURE(9) + errors["NS_ERROR_XSLT_NETWORK_ERROR"] = FAILURE(10) + errors["NS_ERROR_XSLT_WRONG_MIME_TYPE"] = FAILURE(11) + errors["NS_ERROR_XSLT_LOAD_RECURSION"] = FAILURE(12) + errors["NS_ERROR_XPATH_BAD_ARGUMENT_COUNT"] = FAILURE(13) + errors["NS_ERROR_XPATH_BAD_EXTENSION_FUNCTION"] = FAILURE(14) + errors["NS_ERROR_XPATH_PAREN_EXPECTED"] = FAILURE(15) + errors["NS_ERROR_XPATH_INVALID_AXIS"] = FAILURE(16) + errors["NS_ERROR_XPATH_NO_NODE_TYPE_TEST"] = FAILURE(17) + errors["NS_ERROR_XPATH_BRACKET_EXPECTED"] = FAILURE(18) + errors["NS_ERROR_XPATH_INVALID_VAR_NAME"] = FAILURE(19) + errors["NS_ERROR_XPATH_UNEXPECTED_END"] = FAILURE(20) + errors["NS_ERROR_XPATH_OPERATOR_EXPECTED"] = FAILURE(21) + errors["NS_ERROR_XPATH_UNCLOSED_LITERAL"] = FAILURE(22) + errors["NS_ERROR_XPATH_BAD_COLON"] = FAILURE(23) + errors["NS_ERROR_XPATH_BAD_BANG"] = FAILURE(24) + errors["NS_ERROR_XPATH_ILLEGAL_CHAR"] = FAILURE(25) + errors["NS_ERROR_XPATH_BINARY_EXPECTED"] = FAILURE(26) + errors["NS_ERROR_XSLT_LOAD_BLOCKED_ERROR"] = FAILURE(27) + errors["NS_ERROR_XPATH_INVALID_EXPRESSION_EVALUATED"] = FAILURE(28) + errors["NS_ERROR_XPATH_UNBALANCED_CURLY_BRACE"] = FAILURE(29) + errors["NS_ERROR_XSLT_BAD_NODE_NAME"] = FAILURE(30) + errors["NS_ERROR_XSLT_VAR_ALREADY_SET"] = FAILURE(31) + errors["NS_ERROR_XSLT_CALL_TO_KEY_NOT_ALLOWED"] = FAILURE(32) + + errors["NS_XSLT_GET_NEW_HANDLER"] = SUCCESS(1) + + +# ======================================================================= +# 28: NS_ERROR_MODULE_IPC +# ======================================================================= +with modules["IPC"]: + # Initial creation of a Transport object failed internally for unknown reasons. + errors["NS_ERROR_TRANSPORT_INIT"] = FAILURE(1) + # Generic error related to duplicating handle failures. + errors["NS_ERROR_DUPLICATE_HANDLE"] = FAILURE(2) + # Bridging: failure trying to open the connection to the parent + errors["NS_ERROR_BRIDGE_OPEN_PARENT"] = FAILURE(3) + # Bridging: failure trying to open the connection to the child + errors["NS_ERROR_BRIDGE_OPEN_CHILD"] = FAILURE(4) + + +# ======================================================================= +# 30: NS_ERROR_MODULE_STORAGE +# ======================================================================= +with modules["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")' + errors["NS_ERROR_STORAGE_BUSY"] = FAILURE(1) + errors["NS_ERROR_STORAGE_IOERR"] = FAILURE(2) + errors["NS_ERROR_STORAGE_CONSTRAINT"] = FAILURE(3) + + +# ======================================================================= +# 32: NS_ERROR_MODULE_DOM_FILE +# ======================================================================= +with modules["DOM_FILE"]: + errors["NS_ERROR_DOM_FILE_NOT_FOUND_ERR"] = FAILURE(0) + errors["NS_ERROR_DOM_FILE_NOT_READABLE_ERR"] = FAILURE(1) + + +# ======================================================================= +# 33: NS_ERROR_MODULE_DOM_INDEXEDDB +# ======================================================================= +with modules["DOM_INDEXEDDB"]: + # IndexedDB error codes http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html + errors["NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR"] = FAILURE(1) + errors["NS_ERROR_DOM_INDEXEDDB_NOT_FOUND_ERR"] = FAILURE(3) + errors["NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR"] = FAILURE(4) + errors["NS_ERROR_DOM_INDEXEDDB_DATA_ERR"] = FAILURE(5) + errors["NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR"] = FAILURE(6) + errors["NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR"] = FAILURE(7) + errors["NS_ERROR_DOM_INDEXEDDB_ABORT_ERR"] = FAILURE(8) + errors["NS_ERROR_DOM_INDEXEDDB_READ_ONLY_ERR"] = FAILURE(9) + errors["NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR"] = FAILURE(11) + errors["NS_ERROR_DOM_INDEXEDDB_VERSION_ERR"] = FAILURE(12) + errors["NS_ERROR_DOM_INDEXEDDB_KEY_ERR"] = FAILURE(1002) + errors["NS_ERROR_DOM_INDEXEDDB_RENAME_OBJECT_STORE_ERR"] = FAILURE(1003) + errors["NS_ERROR_DOM_INDEXEDDB_RENAME_INDEX_ERR"] = FAILURE(1004) + + +# ======================================================================= +# 34: NS_ERROR_MODULE_DOM_FILEHANDLE +# ======================================================================= +with modules["DOM_FILEHANDLE"]: + errors["NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR"] = FAILURE(1) + errors["NS_ERROR_DOM_FILEHANDLE_NOT_ALLOWED_ERR"] = FAILURE(2) + errors["NS_ERROR_DOM_FILEHANDLE_INACTIVE_ERR"] = FAILURE(3) + errors["NS_ERROR_DOM_FILEHANDLE_ABORT_ERR"] = FAILURE(4) + errors["NS_ERROR_DOM_FILEHANDLE_READ_ONLY_ERR"] = FAILURE(5) + errors["NS_ERROR_DOM_FILEHANDLE_QUOTA_ERR"] = FAILURE(6) + + +# ======================================================================= +# 35: NS_ERROR_MODULE_SIGNED_JAR +# ======================================================================= +with modules["SIGNED_JAR"]: + errors["NS_ERROR_SIGNED_JAR_NOT_SIGNED"] = FAILURE(1) + errors["NS_ERROR_SIGNED_JAR_MODIFIED_ENTRY"] = FAILURE(2) + errors["NS_ERROR_SIGNED_JAR_UNSIGNED_ENTRY"] = FAILURE(3) + errors["NS_ERROR_SIGNED_JAR_ENTRY_MISSING"] = FAILURE(4) + errors["NS_ERROR_SIGNED_JAR_WRONG_SIGNATURE"] = FAILURE(5) + errors["NS_ERROR_SIGNED_JAR_ENTRY_TOO_LARGE"] = FAILURE(6) + errors["NS_ERROR_SIGNED_JAR_ENTRY_INVALID"] = FAILURE(7) + errors["NS_ERROR_SIGNED_JAR_MANIFEST_INVALID"] = FAILURE(8) + + +# ======================================================================= +# 36: NS_ERROR_MODULE_DOM_FILESYSTEM +# ======================================================================= +with modules["DOM_FILESYSTEM"]: + errors["NS_ERROR_DOM_FILESYSTEM_INVALID_PATH_ERR"] = FAILURE(1) + errors["NS_ERROR_DOM_FILESYSTEM_INVALID_MODIFICATION_ERR"] = FAILURE(2) + errors["NS_ERROR_DOM_FILESYSTEM_NO_MODIFICATION_ALLOWED_ERR"] = FAILURE(3) + errors["NS_ERROR_DOM_FILESYSTEM_PATH_EXISTS_ERR"] = FAILURE(4) + errors["NS_ERROR_DOM_FILESYSTEM_TYPE_MISMATCH_ERR"] = FAILURE(5) + errors["NS_ERROR_DOM_FILESYSTEM_UNKNOWN_ERR"] = FAILURE(6) + + +# ======================================================================= +# 38: NS_ERROR_MODULE_SIGNED_APP +# ======================================================================= +with modules["SIGNED_APP"]: + errors["NS_ERROR_SIGNED_APP_MANIFEST_INVALID"] = FAILURE(1) + + +# ======================================================================= +# 40: NS_ERROR_MODULE_DOM_PUSH +# ======================================================================= +with modules["DOM_PUSH"]: + errors["NS_ERROR_DOM_PUSH_DENIED_ERR"] = FAILURE(2) + errors["NS_ERROR_DOM_PUSH_ABORT_ERR"] = FAILURE(3) + errors["NS_ERROR_DOM_PUSH_SERVICE_UNREACHABLE"] = FAILURE(4) + errors["NS_ERROR_DOM_PUSH_INVALID_KEY_ERR"] = FAILURE(5) + errors["NS_ERROR_DOM_PUSH_MISMATCHED_KEY_ERR"] = FAILURE(6) + + +# ======================================================================= +# 41: NS_ERROR_MODULE_DOM_MEDIA +# ======================================================================= +with modules["DOM_MEDIA"]: + # HTMLMediaElement API errors from + # https://html.spec.whatwg.org/multipage/embedded-content.html#media-elements + errors["NS_ERROR_DOM_MEDIA_ABORT_ERR"] = FAILURE(1) + errors["NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR"] = FAILURE(2) + errors["NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR"] = FAILURE(3) + + # HTMLMediaElement internal decoding error + errors["NS_ERROR_DOM_MEDIA_DECODE_ERR"] = FAILURE(4) + errors["NS_ERROR_DOM_MEDIA_FATAL_ERR"] = FAILURE(5) + errors["NS_ERROR_DOM_MEDIA_METADATA_ERR"] = FAILURE(6) + errors["NS_ERROR_DOM_MEDIA_OVERFLOW_ERR"] = FAILURE(7) + errors["NS_ERROR_DOM_MEDIA_END_OF_STREAM"] = FAILURE(8) + errors["NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA"] = FAILURE(9) + errors["NS_ERROR_DOM_MEDIA_CANCELED"] = FAILURE(10) + errors["NS_ERROR_DOM_MEDIA_MEDIASINK_ERR"] = FAILURE(11) + errors["NS_ERROR_DOM_MEDIA_DEMUXER_ERR"] = FAILURE(12) + errors["NS_ERROR_DOM_MEDIA_CDM_ERR"] = FAILURE(13) + errors["NS_ERROR_DOM_MEDIA_NEED_NEW_DECODER"] = FAILURE(14) + errors["NS_ERROR_DOM_MEDIA_INITIALIZING_DECODER"] = FAILURE(15) + errors["NS_ERROR_DOM_MEDIA_REMOTE_DECODER_CRASHED_RDD_OR_GPU_ERR"] = FAILURE(16) + errors["NS_ERROR_DOM_MEDIA_REMOTE_DECODER_CRASHED_UTILITY_ERR"] = FAILURE(17) + errors["NS_ERROR_DOM_MEDIA_REMOTE_DECODER_CRASHED_MF_CDM_ERR"] = FAILURE(18) + + # QuotaExceededError specializations + errors["NS_ERROR_DOM_MEDIA_KEY_QUOTA_EXCEEDED_ERR"] = FAILURE(30) + errors["NS_ERROR_DOM_MEDIA_SOURCE_MAX_BUFFER_QUOTA_EXCEEDED_ERR"] = FAILURE(31) + errors["NS_ERROR_DOM_MEDIA_SOURCE_FULL_BUFFER_QUOTA_EXCEEDED_ERR"] = FAILURE(32) + + # Internal CDM error + errors["NS_ERROR_DOM_MEDIA_CDM_NO_SESSION_ERR"] = FAILURE(50) + errors["NS_ERROR_DOM_MEDIA_CDM_SESSION_OPERATION_ERR"] = FAILURE(51) + errors["NS_ERROR_DOM_MEDIA_CDM_HDCP_NOT_SUPPORT"] = FAILURE(52) + + # Internal platform-related errors + errors["NS_ERROR_DOM_MEDIA_CUBEB_INITIALIZATION_ERR"] = FAILURE(101) + errors["NS_ERROR_DOM_MEDIA_EXTERNAL_ENGINE_NOT_SUPPORTED_ERR"] = FAILURE(102) + errors["NS_ERROR_DOM_MEDIA_CDM_PROXY_NOT_SUPPORTED_ERR"] = FAILURE(103) + errors["NS_ERROR_DOM_MEDIA_DENIED_IN_NON_UTILITY"] = FAILURE(104) + +# ======================================================================= +# 42: NS_ERROR_MODULE_URL_CLASSIFIER +# ======================================================================= +with modules["URL_CLASSIFIER"]: + # Errors during list updates + errors["NS_ERROR_UC_UPDATE_UNKNOWN"] = FAILURE(1) + errors["NS_ERROR_UC_UPDATE_DUPLICATE_PREFIX"] = FAILURE(2) + errors["NS_ERROR_UC_UPDATE_INFINITE_LOOP"] = FAILURE(3) + errors["NS_ERROR_UC_UPDATE_WRONG_REMOVAL_INDICES"] = FAILURE(4) + errors["NS_ERROR_UC_UPDATE_CHECKSUM_MISMATCH"] = FAILURE(5) + errors["NS_ERROR_UC_UPDATE_MISSING_CHECKSUM"] = FAILURE(6) + errors["NS_ERROR_UC_UPDATE_SHUTDOWNING"] = FAILURE(7) + errors["NS_ERROR_UC_UPDATE_TABLE_NOT_FOUND"] = FAILURE(8) + errors["NS_ERROR_UC_UPDATE_BUILD_PREFIX_FAILURE"] = FAILURE(9) + errors["NS_ERROR_UC_UPDATE_FAIL_TO_WRITE_DISK"] = FAILURE(10) + errors["NS_ERROR_UC_UPDATE_UNEXPECTED_VERSION"] = FAILURE(11) + + # Specific errors while parsing pver2/pver4 responses + errors["NS_ERROR_UC_PARSER_MISSING_PARAM"] = FAILURE(12) + errors["NS_ERROR_UC_PARSER_DECODE_FAILURE"] = FAILURE(13) + errors["NS_ERROR_UC_PARSER_UNKNOWN_THREAT"] = FAILURE(14) + errors["NS_ERROR_UC_PARSER_MISSING_VALUE"] = FAILURE(15) + + +# ======================================================================= +# 43: NS_ERROR_MODULE_ERRORRESULT +# ======================================================================= +with modules["ERRORRESULT"]: + # Represents a JS Value being thrown as an exception. + errors["NS_ERROR_INTERNAL_ERRORRESULT_JS_EXCEPTION"] = FAILURE(1) + # Used to indicate that we want to throw a DOMException. + errors["NS_ERROR_INTERNAL_ERRORRESULT_DOMEXCEPTION"] = FAILURE(2) + # Used to indicate that an exception is already pending on the JSContext. + errors["NS_ERROR_INTERNAL_ERRORRESULT_EXCEPTION_ON_JSCONTEXT"] = FAILURE(3) + # Used to indicate that we want to throw a TypeError. + errors["NS_ERROR_INTERNAL_ERRORRESULT_TYPEERROR"] = FAILURE(4) + # Used to indicate that we want to throw a RangeError. + errors["NS_ERROR_INTERNAL_ERRORRESULT_RANGEERROR"] = FAILURE(5) + + +# ======================================================================= +# 45: NS_ERROR_MODULE_WDBA +# ======================================================================= +with modules["WDBA"]: + errors["NS_ERROR_WDBA_NO_PROGID"] = FAILURE(1) + errors["NS_ERROR_WDBA_HASH_CHECK"] = FAILURE(2) + errors["NS_ERROR_WDBA_REJECTED"] = FAILURE(3) + errors["NS_ERROR_WDBA_BUILD"] = FAILURE(4) + + +# ======================================================================= +# 51: NS_ERROR_MODULE_GENERAL +# ======================================================================= +with modules["GENERAL"]: + # Error code used internally by the incremental downloader to cancel the + # network channel when the download is already complete. + errors["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. + errors["NS_ERROR_DOWNLOAD_NOT_PARTIAL"] = FAILURE(2) + errors["NS_ERROR_UNORM_MOREOUTPUT"] = FAILURE(33) + + errors["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 + errors["NS_ERROR_DOCUMENT_IS_PRINTMODE"] = FAILURE(2001) + + errors["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. + errors["NS_SUCCESS_RESTART_APP"] = SUCCESS(1) + + # a11y + # raised when current pivot's position is needed but it's not in the tree + errors["NS_ERROR_NOT_IN_TREE"] = FAILURE(38) + + # see nsTextEquivUtils + errors["NS_OK_NO_NAME_CLAUSE_HANDLED"] = SUCCESS(34) + + # Error code used to indicate that functionality has been blocked by the + # Policy Manager + errors["NS_ERROR_BLOCKED_BY_POLICY"] = FAILURE(3) + + +# ============================================================================ +# Write out the resulting module declarations to C++ and rust files +# ============================================================================ + + +def error_list_h(output): + output.write( + """ +/* THIS FILE IS GENERATED BY ErrorList.py - DO NOT EDIT */ + +#ifndef ErrorList_h__ +#define ErrorList_h__ + +#include <stdint.h> + +""" + ) + + output.write("#define NS_ERROR_MODULE_BASE_OFFSET {}\n".format(MODULE_BASE_OFFSET)) + + for mod, val in modules.items(): + output.write("#define NS_ERROR_MODULE_{} {}\n".format(mod, val.num)) + + items = [] + for error, val in errors.items(): + items.append(" {} = 0x{:X}".format(error, val)) + output.write( + """ +enum class nsresult : uint32_t +{{ +{} +}}; + +""".format( + ",\n".join(items) + ) + ) + + items = [] + for error, val in errors.items(): + items.append(" {0} = nsresult::{0}".format(error)) + + output.write( + """ +const nsresult +{} +; + +#endif // ErrorList_h__ +""".format( + ",\n".join(items) + ) + ) + + +def error_names_internal_h(output): + """Generate ErrorNamesInternal.h, which is a header file declaring one + function, const char* GetErrorNameInternal(nsresult). This method is not + intended to be used by consumer code, which should instead call + GetErrorName in ErrorNames.h.""" + + output.write( + """ +/* THIS FILE IS GENERATED BY ErrorList.py - DO NOT EDIT */ + +#ifndef ErrorNamesInternal_h__ +#define ErrorNamesInternal_h__ + +#include "ErrorNames.h" + +namespace { + +const char* +GetErrorNameInternal(nsresult rv) +{ + switch (rv) { +""" + ) + + # NOTE: Making sure we don't write out duplicate values is important as + # we're using a switch statement to implement this. + seen = set() + for error, val in errors.items(): + if val not in seen: + output.write(' case nsresult::{0}: return "{0}";\n'.format(error)) + seen.add(val) + + output.write( + """ + default: return nullptr; + } +} // GetErrorNameInternal + +} // namespace + +#endif // ErrorNamesInternal_h__ +""" + ) + + +def error_list_rs(output): + output.write( + """ +/* THIS FILE IS GENERATED BY ErrorList.py - DO NOT EDIT */ + +use super::nsresult; + +""" + ) + + output.write( + "pub const NS_ERROR_MODULE_BASE_OFFSET: nsresult = nsresult({});\n".format( + MODULE_BASE_OFFSET + ) + ) + + for mod, val in modules.items(): + output.write( + "pub const NS_ERROR_MODULE_{}: nsresult = nsresult({});\n".format( + mod, val.num + ) + ) + + for error, val in errors.items(): + output.write("pub const {}: nsresult = nsresult(0x{:X});\n".format(error, val)) + + +def gen_jinja(output, input_filename): + # This is used to generate Java code for error lists, and can be expanded to + # other required contexts in the future if desired. + import os + + from jinja2 import Environment, FileSystemLoader, StrictUndefined + + # FileSystemLoader requires the path to the directory containing templates, + # not the file name of the template itself. + (path, leaf) = os.path.split(input_filename) + env = Environment( + loader=FileSystemLoader(path, encoding="utf-8"), + undefined=StrictUndefined, + ) + tpl = env.get_template(leaf) + + context = { + "MODULE_BASE_OFFSET": MODULE_BASE_OFFSET, + "modules": ((mod, val.num) for mod, val in modules.items()), + "errors": errors.items(), + } + + tpl.stream(context).dump(output, encoding="utf-8") diff --git a/xpcom/base/ErrorNames.cpp b/xpcom/base/ErrorNames.cpp new file mode 100644 index 0000000000..42401aa860 --- /dev/null +++ b/xpcom/base/ErrorNames.cpp @@ -0,0 +1,78 @@ +/* -*- 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" +#include "MainThreadUtils.h" + +// Get the GetErrorNameInternal method +#include "ErrorNamesInternal.h" + +namespace mozilla { + +const char* GetStaticErrorName(nsresult rv) { return GetErrorNameInternal(rv); } + +void GetErrorName(nsresult rv, nsACString& name) { + if (const char* errorName = GetErrorNameInternal(rv)) { + name.AssignASCII(errorName); + 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); + + if (NS_SUCCEEDED(rv)) { + name.AssignLiteral("NS_ERROR_GENERATE_SUCCESS("); + } else { + name.AssignLiteral("NS_ERROR_GENERATE_FAILURE("); + } + + if (isSecurityError) { + name.AppendLiteral("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.AppendLiteral(", "); + + const char* nsprName = nullptr; + if (isSecurityError && NS_IsMainThread()) { + // 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.AppendLiteral(")"); +} + +} // namespace mozilla + +extern "C" { + +// This is an extern "C" binding for the GetErrorName method which is used by +// the nsresult rust bindings in xpcom/rust/nserror. +void Gecko_GetErrorName(nsresult aRv, nsACString& aName) { + mozilla::GetErrorName(aRv, aName); +} +} diff --git a/xpcom/base/ErrorNames.h b/xpcom/base/ErrorNames.h new file mode 100644 index 0000000000..d209cc492b --- /dev/null +++ b/xpcom/base/ErrorNames.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_ErrorNames_h +#define mozilla_ErrorNames_h + +#include "nsError.h" +#include "nsStringFwd.h" + +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); + +// Same as GetErrorName, except that only nsresult values with statically +// known symbolic names are handled. For all other values, nullptr is +// returned. +const char* GetStaticErrorName(nsresult rv); + +} // namespace mozilla + +#endif // mozilla_ErrorNames_h diff --git a/xpcom/base/GkRustUtils.cpp b/xpcom/base/GkRustUtils.cpp new file mode 100644 index 0000000000..00d32e6f65 --- /dev/null +++ b/xpcom/base/GkRustUtils.cpp @@ -0,0 +1,16 @@ +/* -*- 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 "gk_rust_utils_ffi_generated.h" +#include "GkRustUtils.h" + +using namespace mozilla; + +/* static */ +bool GkRustUtils::ParseSemVer(const nsACString& aVersion, uint64_t& aOutMajor, + uint64_t& aOutMinor, uint64_t& aOutPatch) { + return GkRustUtils_ParseSemVer(&aVersion, &aOutMajor, &aOutMinor, &aOutPatch); +} diff --git a/xpcom/base/GkRustUtils.h b/xpcom/base/GkRustUtils.h new file mode 100644 index 0000000000..b7f8c3b99c --- /dev/null +++ b/xpcom/base/GkRustUtils.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_GkRustUtils_h +#define __mozilla_GkRustUtils_h + +#include "nsString.h" + +class GkRustUtils { + public: + static bool ParseSemVer(const nsACString& aVersion, uint64_t& aOutMajor, + uint64_t& aOutMinor, uint64_t& aOutPatch); +}; + +#endif diff --git a/xpcom/base/HoldDropJSObjects.cpp b/xpcom/base/HoldDropJSObjects.cpp new file mode 100644 index 0000000000..2b3a810d89 --- /dev/null +++ b/xpcom/base/HoldDropJSObjects.cpp @@ -0,0 +1,53 @@ +/* -*- 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/CycleCollectedJSRuntime.h" + +namespace mozilla { +namespace cyclecollector { + +void HoldJSObjectsImpl(void* aHolder, nsScriptObjectTracer* aTracer, + JS::Zone* aZone) { + CycleCollectedJSRuntime* rt = CycleCollectedJSRuntime::Get(); + MOZ_ASSERT(rt, "Should have a CycleCollectedJSRuntime by now"); + rt->AddJSHolder(aHolder, aTracer, aZone); +} + +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) { + CycleCollectedJSRuntime* rt = CycleCollectedJSRuntime::Get(); + MOZ_ASSERT(rt, "Should have a CycleCollectedJSRuntime by now"); + rt->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 + +} // namespace mozilla diff --git a/xpcom/base/HoldDropJSObjects.h b/xpcom/base/HoldDropJSObjects.h new file mode 100644 index 0000000000..e01590208f --- /dev/null +++ b/xpcom/base/HoldDropJSObjects.h @@ -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/. */ + +#ifndef mozilla_HoldDropJSObjects_h +#define mozilla_HoldDropJSObjects_h + +#include <type_traits> +#include "nsCycleCollectionNoteChild.h" + +class nsISupports; +class nsScriptObjectTracer; +class nsCycleCollectionParticipant; + +namespace JS { +class Zone; +} + +// Only HoldJSObjects and DropJSObjects should be called directly. + +namespace mozilla { +namespace cyclecollector { + +void HoldJSObjectsImpl(void* aHolder, nsScriptObjectTracer* aTracer, + JS::Zone* aZone = nullptr); +void HoldJSObjectsImpl(nsISupports* aHolder); +void DropJSObjectsImpl(void* aHolder); +void DropJSObjectsImpl(nsISupports* aHolder); + +} // namespace cyclecollector + +template <class T, bool isISupports = std::is_base_of<nsISupports, T>::value, + typename P = typename T::NS_CYCLE_COLLECTION_INNERCLASS> +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)); + } +}; + +/** + Classes that hold strong references to JS GC things such as `JSObjects` and + `JS::Values` (e.g. `JS::Heap<JSObject*> mFoo;`) must use these, generally by + calling `HoldJSObjects(this)` and `DropJSObjects(this)` in the ctor and dtor + respectively. + + For classes that are wrapper cached and hold no other strong references to JS + GC things, there's no need to call these; it will be taken care of + automatically by nsWrapperCache. +**/ +template <class T> +void HoldJSObjects(T* aHolder) { + static_assert(!std::is_base_of<nsCycleCollectionParticipant, T>::value, + "Don't call this on the CC participant but on the object that " + "it's for (in an Unlink implementation it's usually stored in " + "a variable named 'tmp')."); + HoldDropJSObjectsHelper<T>::Hold(aHolder); +} + +template <class T> +void DropJSObjects(T* aHolder) { + static_assert(!std::is_base_of<nsCycleCollectionParticipant, T>::value, + "Don't call this on the CC participant but on the object that " + "it's for (in an Unlink implementation it's usually stored in " + "a variable named 'tmp')."); + HoldDropJSObjectsHelper<T>::Drop(aHolder); +} + +} // namespace mozilla + +#endif // mozilla_HoldDropJSObjects_h diff --git a/xpcom/base/IntentionalCrash.h b/xpcom/base/IntentionalCrash.h new file mode 100644 index 0000000000..3ee751f7c7 --- /dev/null +++ b/xpcom/base/IntentionalCrash.h @@ -0,0 +1,71 @@ +/* -*- 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_IntentionalCrash_h +#define mozilla_IntentionalCrash_h + +#include <string> +#include <sstream> +#include <stdlib.h> +#include <stdio.h> + +#ifdef XP_WIN +# include <process.h> +# define getpid _getpid +#else +# include <unistd.h> +#endif + +namespace mozilla { + +inline void NoteIntentionalCrash(const char* aProcessType, uint32_t aPid = 0) { +// In opt builds we don't actually have the leak checking enabled, and the +// sandbox doesn't allow writing to this path, so we just disable this +// function's behaviour. +#ifdef MOZ_DEBUG + char* f = getenv("XPCOM_MEM_BLOAT_LOG"); + if (!f) { + return; + } + + fprintf(stderr, "XPCOM_MEM_BLOAT_LOG: %s\n", f); + + uint32_t processPid = aPid == 0 ? getpid() : aPid; + + std::ostringstream bloatName; + std::string processType(aProcessType); + if (!processType.compare("default")) { + bloatName << f; + } else { + std::string bloatLog(f); + + bool hasExt = false; + if (bloatLog.size() >= 4 && + bloatLog.compare(bloatLog.size() - 4, 4, ".log", 4) == 0) { + hasExt = true; + bloatLog.erase(bloatLog.size() - 4, 4); + } + + bloatName << bloatLog << "_" << processType << "_pid" << processPid; + if (hasExt) { + bloatName << ".log"; + } + } + + fprintf(stderr, "Writing to log: %s\n", bloatName.str().c_str()); + + FILE* processfd = fopen(bloatName.str().c_str(), "a"); + if (processfd) { + fprintf(processfd, "\n==> process %d will purposefully crash\n", + processPid); + fclose(processfd); + } +#endif +} + +} // namespace mozilla + +#endif // mozilla_IntentionalCrash_h diff --git a/xpcom/base/JSONStringWriteFuncs.h b/xpcom/base/JSONStringWriteFuncs.h new file mode 100644 index 0000000000..65b688788c --- /dev/null +++ b/xpcom/base/JSONStringWriteFuncs.h @@ -0,0 +1,52 @@ +/* -*- 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 JSONSTRINGWRITEFUNCS_H +#define JSONSTRINGWRITEFUNCS_H + +#include "mozilla/JSONWriter.h" +#include "nsString.h" + +#include <type_traits> + +namespace mozilla { + +// JSONWriteFunc that writes to an owned string. +template <typename StringType> +class JSONStringWriteFunc final : public JSONWriteFunc { + static_assert( + !std::is_reference_v<StringType>, + "Use JSONStringRefWriteFunc instead to write to a referenced string"); + + public: + JSONStringWriteFunc() = default; + + void Write(const Span<const char>& aStr) final { mString.Append(aStr); } + + const StringType& StringCRef() const { return mString; } + + StringType&& StringRRef() && { return std::move(mString); } + + private: + StringType mString; +}; + +// JSONWriteFunc that writes to a given nsACString reference. +class JSONStringRefWriteFunc final : public JSONWriteFunc { + public: + MOZ_IMPLICIT JSONStringRefWriteFunc(nsACString& aString) : mString(aString) {} + + void Write(const Span<const char>& aStr) final { mString.Append(aStr); } + + const nsACString& StringCRef() const { return mString; } + + private: + nsACString& mString; +}; + +} // namespace mozilla + +#endif // JSONSTRINGWRITEFUNCS_H diff --git a/xpcom/base/JSObjectHolder.cpp b/xpcom/base/JSObjectHolder.cpp new file mode 100644 index 0000000000..5bcc3cabbf --- /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 0000000000..d64d41e479 --- /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() = default; + + JS::PersistentRooted<JSObject*> mJSObject; +}; + +} // namespace mozilla + +#endif // mozilla_JSObjectHolder_h diff --git a/xpcom/base/LogCommandLineHandler.cpp b/xpcom/base/LogCommandLineHandler.cpp new file mode 100644 index 0000000000..26e78670e9 --- /dev/null +++ b/xpcom/base/LogCommandLineHandler.cpp @@ -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 "LogCommandLineHandler.h" + +#include "mozilla/Tokenizer.h" +#include "nsDebug.h" + +namespace mozilla { + +void LoggingHandleCommandLineArgs( + int argc, char const* const* argv, + std::function<void(nsACString const&)> const& consumer) { + // Keeps the name of a pending env var (MOZ_LOG or MOZ_LOG_FILE) that + // we expect to get a value for in the next iterated arg. + // Used for the `-MOZ_LOG <modules>` form of argument. + nsAutoCString env; + + auto const names = {"MOZ_LOG"_ns, "MOZ_LOG_FILE"_ns}; + + for (int arg = 1; arg < argc; ++arg) { + Tokenizer p(argv[arg]); + + if (!env.IsEmpty() && p.CheckChar('-')) { + NS_WARNING( + "Expects value after -MOZ_LOG(_FILE) argument, but another argument " + "found"); + + // We only expect values for the pending env var, start over + p.Rollback(); + env.Truncate(); + } + + if (env.IsEmpty()) { + if (!p.CheckChar('-')) { + continue; + } + // We accept `-MOZ_LOG` as well as `--MOZ_LOG`. + Unused << p.CheckChar('-'); + + for (auto const& name : names) { + if (!p.CheckWord(name)) { + continue; + } + + env.Assign(name); + env.Append('='); + break; + } + + if (env.IsEmpty()) { + // An unknonwn argument, ignore. + continue; + } + + // We accept `-MOZ_LOG <modules>` as well as `-MOZ_LOG=<modules>`. + + if (p.CheckEOF()) { + // We have a lone `-MOZ_LOG` arg, the next arg is expected to be + // the value, |env| is now prepared as `MOZ_LOG=`. + continue; + } + + if (!p.CheckChar('=')) { + // There is a character after the arg name and it's not '=', + // ignore this argument. + NS_WARNING("-MOZ_LOG(_FILE) argument not in a proper form"); + + env.Truncate(); + continue; + } + } + + // This can be non-empty from previous iteration or in this iteration. + if (!env.IsEmpty()) { + nsDependentCSubstring value; + Unused << p.ReadUntil(Tokenizer::Token::EndOfFile(), value); + env.Append(value); + + consumer(env); + + env.Truncate(); + } + } +} + +} // namespace mozilla diff --git a/xpcom/base/LogCommandLineHandler.h b/xpcom/base/LogCommandLineHandler.h new file mode 100644 index 0000000000..ef945d7980 --- /dev/null +++ b/xpcom/base/LogCommandLineHandler.h @@ -0,0 +1,49 @@ +/* -*- 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 LogCommandLineHandler_h +#define LogCommandLineHandler_h + +#include <functional> +#include "nsString.h" + +namespace mozilla { + +/** + * A helper function parsing provided command line arguments and handling two + * specific args: + * + * -MOZ_LOG=modulelist + * -MOZ_LOG_FILE=file/path + * + * both expecting an argument, and corresponding to the same-name environment + * variables we use for logging setup. + * + * When an argument is found in the proper form, the consumer callback is called + * with a string in a follwing form, note that we do this for every occurence, + * and not just at the end of the parsing: + * + * "MOZ_LOG=modulelist" or "MOZ_LOG_FILE=file/path" + * + * All the following forms of arguments of the application are possible: + * + * --MOZ_LOG modulelist + * -MOZ_LOG modulelist + * --MOZ_LOG=modulelist + * -MOZ_LOG=modulelist + * + * The motivation for a separte function and not implementing a command line + * handler interface is that we need to process this very early during the + * application startup. Command line handlers are proccessed way later + * after logging has already been set up. + */ +void LoggingHandleCommandLineArgs( + int argc, char const* const* argv, + std::function<void(nsACString const&)> const& consumer); + +} // namespace mozilla + +#endif diff --git a/xpcom/base/LogModulePrefWatcher.cpp b/xpcom/base/LogModulePrefWatcher.cpp new file mode 100644 index 0000000000..bd06660533 --- /dev/null +++ b/xpcom/base/LogModulePrefWatcher.cpp @@ -0,0 +1,164 @@ +/* -*- 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 "mozilla/Services.h" +#include "nsIObserverService.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"; +static const char kLoggingPrefStacks[] = "logging.config.profilerstacks"; + +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. + */ +static void ResetExistingPrefs() { + nsTArray<nsCString> names; + nsresult rv = + Preferences::GetRootBranch()->GetChildList(kLoggingPrefPrefix, names); + if (NS_SUCCEEDED(rv)) { + for (auto& name : names) { + // Clearing the pref will cause it to reload, thus resetting the log level + Preferences::ClearUser(name.get()); + } + } +} + +/** + * 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(), MOZ_LOG_PID_TOKEN)) { + prefValue.AppendLiteral(MOZ_LOG_PID_TOKEN); + } + + 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); + } else if (prefName.EqualsLiteral(kLoggingPrefStacks)) { + bool captureStacks = Preferences::GetBool(aName, false); + LogModule::SetCaptureStacks(captureStacks); + } + 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); +} + +static void LoadExistingPrefs() { + nsIPrefBranch* root = Preferences::GetRootBranch(); + if (!root) { + return; + } + + nsTArray<nsCString> names; + nsresult rv = root->GetChildList(kLoggingPrefPrefix, names); + if (NS_SUCCEEDED(rv)) { + for (auto& name : names) { + LoadPrefValue(name.get()); + } + } +} + +LogModulePrefWatcher::LogModulePrefWatcher() = default; + +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 0000000000..92a2a3d5b7 --- /dev/null +++ b/xpcom/base/LogModulePrefWatcher.h @@ -0,0 +1,39 @@ +/* -*- 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() = default; +}; +} // namespace mozilla + +#endif // LogModulePrefWatcher_h diff --git a/xpcom/base/Logging.cpp b/xpcom/base/Logging.cpp new file mode 100644 index 0000000000..61899b77c7 --- /dev/null +++ b/xpcom/base/Logging.cpp @@ -0,0 +1,913 @@ +/* -*- 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 "base/process_util.h" +#include "GeckoProfiler.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/FileUtils.h" +#include "mozilla/LateWriteChecks.h" +#include "mozilla/Mutex.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/Printf.h" +#include "mozilla/Atomics.h" +#include "mozilla/Sprintf.h" +#include "mozilla/UniquePtrExtensions.h" +#include "MainThreadUtils.h" +#include "nsClassHashtable.h" +#include "nsDebug.h" +#include "nsDebugImpl.h" +#include "nsPrintfCString.h" +#include "NSPRLogModulesParser.h" +#include "nsXULAppAPI.h" +#include "LogCommandLineHandler.h" + +#include "prenv.h" +#ifdef XP_WIN +# include <fcntl.h> +# include <process.h> +#else +# include <sys/stat.h> // for umask() +# include <sys/types.h> +# include <unistd.h> +#endif + +// NB: Amount determined by performing a typical browsing session and finding +// the maximum number of modules instantiated, 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. +// +// (Note: When this is changed to be >= 10, SandboxBroker::LaunchApp must add +// another rule to allow logfile.?? be written by content processes.) +const uint32_t kRotateFilesNumber = 4; + +namespace mozilla { + +namespace detail { + +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); +} + +void log_print(const LogModule* aModule, LogLevel aLevel, TimeStamp* aStart, + const char* aFmt, ...) { + va_list ap; + va_start(ap, aFmt); + aModule->Printv(aLevel, aStart, aFmt, ap); + va_end(ap); +} + +} // namespace 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); +} + +static 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; +}; + +static const char* ExpandLogFileName(const char* aFilename, + char (&buffer)[2048]) { + MOZ_ASSERT(aFilename); + static const char kPIDToken[] = MOZ_LOG_PID_TOKEN; + static const char kMOZLOGExt[] = MOZ_LOG_FILE_EXTENSION; + + bool hasMozLogExtension = StringEndsWith(nsDependentCString(aFilename), + nsLiteralCString(kMOZLOGExt)); + + const char* pidTokenPtr = strstr(aFilename, kPIDToken); + if (pidTokenPtr && + SprintfLiteral(buffer, "%.*s%s%" PRIPID "%s%s", + static_cast<int>(pidTokenPtr - aFilename), aFilename, + XRE_IsParentProcess() ? "-main." : "-child.", + base::GetCurrentProcId(), pidTokenPtr + strlen(kPIDToken), + hasMozLogExtension ? "" : kMOZLOGExt) > 0) { + return buffer; + } + + if (!hasMozLogExtension && + SprintfLiteral(buffer, "%s%s", aFilename, kMOZLOGExt) > 0) { + return buffer; + } + + return aFilename; +} + +// Drop initial lines from the given file until it is less than or equal to the +// given size. +// +// For simplicity and to reduce memory consumption, lines longer than the given +// long line size may be broken. +// +// This function uses `mkstemp` and `rename` on POSIX systems and `_mktemp_s` +// and `ReplaceFileA` on Win32 systems. `ReplaceFileA` was introduced in +// Windows 7 so it's available. +bool LimitFileToLessThanSize(const char* aFilename, uint32_t aSize, + uint16_t aLongLineSize = 16384) { + // `tempFilename` will be further updated below. + char tempFilename[2048]; + SprintfLiteral(tempFilename, "%s.tempXXXXXX", aFilename); + + bool failedToWrite = false; + + { // Scope `file` and `temp`, so that they are definitely closed. + ScopedCloseFile file(fopen(aFilename, "rb")); + if (!file) { + return false; + } + + if (fseek(file.get(), 0, SEEK_END)) { + // If we can't seek for some reason, better to just not limit the log at + // all and hope to sort out large logs upon further analysis. + return false; + } + + // `ftell` returns a positive `long`, which might be more than 32 bits. + uint64_t fileSize = static_cast<uint64_t>(ftell(file.get())); + + if (fileSize <= aSize) { + return true; + } + + uint64_t minBytesToDrop = fileSize - aSize; + uint64_t numBytesDropped = 0; + + if (fseek(file.get(), 0, SEEK_SET)) { + // Same as above: if we can't seek, hope for the best. + return false; + } + + ScopedCloseFile temp; + +#if defined(XP_WIN) + // This approach was cribbed from + // https://searchfox.org/mozilla-central/rev/868935867c6241e1302e64cf9be8f56db0fd0d1c/xpcom/build/LateWriteChecks.cpp#158. + HANDLE hFile; + do { + // mkstemp isn't supported so keep trying until we get a file. + _mktemp_s(tempFilename, strlen(tempFilename) + 1); + hFile = CreateFileA(tempFilename, GENERIC_WRITE, 0, nullptr, CREATE_NEW, + FILE_ATTRIBUTE_NORMAL, nullptr); + } while (GetLastError() == ERROR_FILE_EXISTS); + + if (hFile == INVALID_HANDLE_VALUE) { + NS_WARNING("INVALID_HANDLE_VALUE"); + return false; + } + + int fd = _open_osfhandle((intptr_t)hFile, _O_APPEND); + if (fd == -1) { + NS_WARNING("_open_osfhandle failed!"); + return false; + } + + temp.reset(_fdopen(fd, "ab")); +#elif defined(XP_UNIX) + + // Coverity would prefer us to set a secure umask before using `mkstemp`. + // However, the umask is process-wide, so setting it may lead to difficult + // to debug complications; and it is fine for this particular short-lived + // temporary file to be insecure. + // + // coverity[SECURE_TEMP : FALSE] + int fd = mkstemp(tempFilename); + if (fd == -1) { + NS_WARNING("mkstemp failed!"); + return false; + } + temp.reset(fdopen(fd, "ab")); +#else +# error Do not know how to open named temporary file +#endif + + if (!temp) { + NS_WARNING(nsPrintfCString("could not open named temporary file %s", + tempFilename) + .get()); + return false; + } + + // `fgets` always null terminates. If the line is too long, it won't + // include a trailing '\n' but will be null-terminated. + UniquePtr<char[]> line = MakeUnique<char[]>(aLongLineSize + 1); + while (fgets(line.get(), aLongLineSize + 1, file.get())) { + if (numBytesDropped >= minBytesToDrop) { + if (fputs(line.get(), temp.get()) < 0) { + NS_WARNING( + nsPrintfCString("fputs failed: ferror %d\n", ferror(temp.get())) + .get()); + failedToWrite = true; + break; + } + } else { + // Binary mode avoids platform-specific wrinkles with text streams. In + // particular, on Windows, `\r\n` gets read as `\n` (and the reverse + // when writing), complicating this calculation. + numBytesDropped += strlen(line.get()); + } + } + } + + // At this point, `file` and `temp` are closed, so we can remove and rename. + if (failedToWrite) { + remove(tempFilename); + return false; + } + +#if defined(XP_WIN) + if (!::ReplaceFileA(aFilename, tempFilename, nullptr, 0, 0, 0)) { + NS_WARNING( + nsPrintfCString("ReplaceFileA failed: %lu\n", GetLastError()).get()); + return false; + } +#elif defined(XP_UNIX) + if (rename(tempFilename, aFilename)) { + NS_WARNING( + nsPrintfCString("rename failed: %s (%d)\n", strerror(errno), errno) + .get()); + return false; + } +#else +# error Do not know how to atomically replace file +#endif + + return true; +} + +} // namespace 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); +} +} // namespace + +class LogModuleManager { + public: + LogModuleManager() + : mModulesLock("logmodules"), + mModules(kInitialModuleCount), +#ifdef DEBUG + mLoggingModuleRegistered(0), +#endif + mPrintEntryCount(0), + mOutFile(nullptr), + mToReleaseFile(nullptr), + mOutFileNum(0), + mOutFilePath(strdup("")), + mMainThread(PR_GetCurrentThread()), + mSetFromEnv(false), + mAddTimestamp(false), + mCaptureProfilerStack(false), + mIsRaw(false), + mIsSync(false), + mRotate(0), + mInitialized(false) { + } + + ~LogModuleManager() { + detail::LogFile* logFile = mOutFile.exchange(nullptr); + delete logFile; + } + + /** + * Loads config from command line args or env vars if present, in + * this specific order of priority. + * + * Notes: + * + * 1) This function is only intended to be called once per session. + * 2) None of the functions used in Init should rely on logging. + */ + void Init(int argc, char* argv[]) { + MOZ_DIAGNOSTIC_ASSERT(!mInitialized); + mInitialized = true; + + LoggingHandleCommandLineArgs(argc, static_cast<char const* const*>(argv), + [](nsACString const& env) { + // We deliberately set/rewrite the + // environment variables so that when child + // processes are spawned w/o passing the + // arguments they still inherit the logging + // settings as well as sandboxing can be + // correctly set. Scripts can pass + // -MOZ_LOG=$MOZ_LOG,modules as an argument + // to merge existing settings, if required. + + // PR_SetEnv takes ownership of the string. + PR_SetEnv(ToNewCString(env)); + }); + + bool shouldAppend = false; + bool addTimestamp = false; + bool isSync = false; + bool isRaw = false; + bool captureStacks = false; + int32_t rotate = 0; + int32_t maxSize = 0; + bool prependHeader = false; + 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."); + } + } + + // Need to capture `this` since `sLogModuleManager` is not set until after + // initialization is complete. + NSPRLogModulesParser( + modules, + [this, &shouldAppend, &addTimestamp, &isSync, &isRaw, &rotate, &maxSize, + &prependHeader, &captureStacks](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, "raw") == 0) { + isRaw = true; + } else if (strcmp(aName, "rotate") == 0) { + rotate = (aValue << 20) / kRotateFilesNumber; + } else if (strcmp(aName, "maxsize") == 0) { + maxSize = aValue << 20; + } else if (strcmp(aName, "prependheader") == 0) { + prependHeader = true; + } else if (strcmp(aName, "profilerstacks") == 0) { + captureStacks = true; + } else { + this->CreateOrGetModule(aName)->SetLevel(aLevel); + } + }); + + // Rotate implies timestamp to make the files readable + mAddTimestamp = addTimestamp || rotate > 0; + mIsSync = isSync; + mIsRaw = isRaw; + mRotate = rotate; + mCaptureProfilerStack = captureStacks; + + if (rotate > 0 && shouldAppend) { + NS_WARNING("MOZ_LOG: when you rotate the log, you cannot use append!"); + } + + if (rotate > 0 && maxSize > 0) { + NS_WARNING( + "MOZ_LOG: when you rotate the log, you cannot use maxsize! (ignoring " + "maxsize)"); + maxSize = 0; + } + + if (maxSize > 0 && !shouldAppend) { + NS_WARNING( + "MOZ_LOG: when you limit the log to maxsize, you must use append! " + "(ignorning maxsize)"); + maxSize = 0; + } + + if (rotate > 0 && prependHeader) { + NS_WARNING( + "MOZ_LOG: when you rotate the log, you cannot use prependheader!"); + prependHeader = false; + } + + 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::ExpandLogFileName(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, maxSize); + mSetFromEnv = true; + } + + if (prependHeader && XRE_IsParentProcess()) { + va_list va; + empty_va(&va); + Print("Logger", LogLevel::Info, nullptr, "\n***\n\n", "Opening log\n", + va); + } + } + + 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::ExpandLogFileName(filename, buf); + + // Can't use rotate or maxsize 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 SetCaptureStacks(bool aCaptureStacks) { + mCaptureProfilerStack = aCaptureStacks; + } + + void SetAddTimestamp(bool aAddTimestamp) { mAddTimestamp = aAddTimestamp; } + + detail::LogFile* OpenFile(bool aShouldAppend, uint32_t aFileNum, + uint32_t aMaxSize = 0) { + FILE* file; + + if (mRotate > 0) { + char buf[2048]; + SprintfLiteral(buf, "%s.%d", mOutFilePath.get(), aFileNum); + + // rotate doesn't support append (or maxsize). + file = fopen(buf, "w"); + } else if (aShouldAppend && aMaxSize > 0) { + detail::LimitFileToLessThanSize(mOutFilePath.get(), aMaxSize >> 1); + file = fopen(mOutFilePath.get(), "a"); + } 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); + return mModules + .LookupOrInsertWith( + aName, + [&] { +#ifdef DEBUG + if (++mLoggingModuleRegistered > kInitialModuleCount) { + NS_WARNING( + "kInitialModuleCount too low, consider increasing its " + "value"); + } +#endif + return UniquePtr<LogModule>( + new LogModule{aName, LogLevel::Disabled}); + }) + .get(); + } + + void Print(const char* aName, LogLevel aLevel, const char* aFmt, + va_list aArgs) MOZ_FORMAT_PRINTF(4, 0) { + Print(aName, aLevel, nullptr, "", aFmt, aArgs); + } + + void Print(const char* aName, LogLevel aLevel, const TimeStamp* aStart, + const char* aPrepend, const char* aFmt, va_list aArgs) + MOZ_FORMAT_PRINTF(6, 0) { + AutoSuspendLateWriteChecks suspendLateWriteChecks; + long pid = static_cast<long>(base::GetCurrentProcId()); + const size_t kBuffSize = 1024; + char buff[kBuffSize]; + + char* buffToWrite = buff; + SmprintfPointer allocatedBuff; + + va_list argsCopy; + va_copy(argsCopy, aArgs); + int charsWritten = VsprintfLiteral(buff, aFmt, argsCopy); + va_end(argsCopy); + + if (charsWritten < 0) { + // Print out at least something. We must copy to the local buff, + // can't just assign aFmt to buffToWrite, since when + // buffToWrite != buff, we try to release it. + MOZ_ASSERT(false, "Probably incorrect format string in LOG?"); + strncpy(buff, aFmt, kBuffSize - 1); + buff[kBuffSize - 1] = '\0'; + charsWritten = strlen(buff); + } else if (static_cast<size_t>(charsWritten) >= kBuffSize - 1) { + // We may have maxed out, allocate a buffer instead. + allocatedBuff = mozilla::Vsmprintf(aFmt, aArgs); + buffToWrite = allocatedBuff.get(); + charsWritten = strlen(buffToWrite); + } + + if (profiler_thread_is_being_profiled_for_markers()) { + struct LogMarker { + static constexpr Span<const char> MarkerTypeName() { + return MakeStringSpan("Log"); + } + static void StreamJSONMarkerData( + baseprofiler::SpliceableJSONWriter& aWriter, + const ProfilerString8View& aModule, + const ProfilerString8View& aText) { + aWriter.StringProperty("module", aModule); + aWriter.StringProperty("name", aText); + } + static MarkerSchema MarkerTypeDisplay() { + using MS = MarkerSchema; + MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable}; + schema.SetTableLabel("({marker.data.module}) {marker.data.name}"); + schema.AddKeyLabelFormatSearchable("module", "Module", + MS::Format::String, + MS::Searchable::Searchable); + schema.AddKeyLabelFormatSearchable("name", "Name", MS::Format::String, + MS::Searchable::Searchable); + return schema; + } + }; + + profiler_add_marker( + "LogMessages", geckoprofiler::category::OTHER, + {aStart ? MarkerTiming::IntervalUntilNowFrom(*aStart) + : MarkerTiming::InstantNow(), + MarkerStack::MaybeCapture(mCaptureProfilerStack)}, + LogMarker{}, ProfilerString8View::WrapNullTerminatedString(aName), + ProfilerString8View::WrapNullTerminatedString(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 && !aStart) { + if (!mIsRaw) { + fprintf_stderr(out, "%s[%s %ld: %s]: %s/%s %s%s", aPrepend, + nsDebugImpl::GetMultiprocessMode(), pid, + currentThreadName, ToLogStr(aLevel), aName, buffToWrite, + newline); + } else { + fprintf_stderr(out, "%s%s%s", aPrepend, buffToWrite, newline); + } + } else { + if (aStart) { + // XXX is there a reasonable way to convert one to the other? this is + // bad + PRTime prnow = PR_Now(); + TimeStamp tmnow = TimeStamp::Now(); + TimeDuration duration = tmnow - *aStart; + PRTime prstart = prnow - duration.ToMicroseconds(); + + PRExplodedTime now; + PRExplodedTime start; + PR_ExplodeTime(prnow, PR_GMTParameters, &now); + PR_ExplodeTime(prstart, PR_GMTParameters, &start); + // Ignore that the start time might be in a different day + fprintf_stderr( + out, + "%s%04d-%02d-%02d %02d:%02d:%02d.%06d -> %02d:%02d:%02d.%06d UTC " + "(%.1gms)- [%s %ld: %s]: %s/%s %s%s", + aPrepend, now.tm_year, now.tm_month + 1, start.tm_mday, + start.tm_hour, start.tm_min, start.tm_sec, start.tm_usec, + now.tm_hour, now.tm_min, now.tm_sec, now.tm_usec, + duration.ToMilliseconds(), nsDebugImpl::GetMultiprocessMode(), pid, + currentThreadName, ToLogStr(aLevel), aName, buffToWrite, newline); + } else { + PRExplodedTime now; + PR_ExplodeTime(PR_Now(), PR_GMTParameters, &now); + fprintf_stderr(out, + "%s%04d-%02d-%02d %02d:%02d:%02d.%06d UTC - [%s %ld: " + "%s]: %s/%s %s%s", + aPrepend, now.tm_year, now.tm_month + 1, now.tm_mday, + now.tm_hour, now.tm_min, now.tm_sec, now.tm_usec, + nsDebugImpl::GetMultiprocessMode(), pid, + currentThreadName, ToLogStr(aLevel), aName, buffToWrite, + newline); + } + } + + if (mIsSync) { + fflush(out); + } + + 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; + +#ifdef DEBUG + Atomic<uint32_t, ReleaseAcquire> mLoggingModuleRegistered; +#endif + // 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> mCaptureProfilerStack; + Atomic<bool, Relaxed> mIsRaw; + Atomic<bool, Relaxed> mIsSync; + int32_t mRotate; + bool mInitialized; +}; + +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::SetCaptureStacks(bool aCaptureStacks) { + sLogModuleManager->SetCaptureStacks(aCaptureStacks); +} + +// This function is defined in gecko_logger/src/lib.rs +// We mirror the level in rust code so we don't get forwarded all of the +// rust logging and have to create an LogModule for each rust component. +extern "C" void set_rust_log_level(const char* name, uint8_t level); + +void LogModule::SetLevel(LogLevel level) { + mLevel = level; + + // If the log module contains `::` it is likely a rust module, so we + // pass the level into the rust code so it will know to forward the logging + // to Gecko. + if (strstr(mName, "::")) { + set_rust_log_level(mName, static_cast<uint8_t>(level)); + } +} + +void LogModule::Init(int argc, char* argv[]) { + // NB: This method is not threadsafe; it is expected to be called very early + // in startup prior to any other threads being run. + MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread()); + + 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. + + // Don't assign the pointer until after Init is called. This should help us + // detect if any of the functions called by Init somehow rely on logging. + auto mgr = new LogModuleManager(); + mgr->Init(argc, argv); + sLogModuleManager = mgr; +} + +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); +} + +void LogModule::Printv(LogLevel aLevel, const TimeStamp* aStart, + const char* aFmt, va_list aArgs) const { + MOZ_ASSERT(sLogModuleManager != nullptr); + + // Forward to LogModule manager w/ level and name + sLogModuleManager->Print(Name(), aLevel, aStart, "", aFmt, aArgs); +} + +} // namespace mozilla + +extern "C" { + +// This function is called by external code (rust) to log to one of our +// log modules. +void ExternMozLog(const char* aModule, mozilla::LogLevel aLevel, + const char* aMsg) { + MOZ_ASSERT(mozilla::sLogModuleManager != nullptr); + + mozilla::LogModule* m = + mozilla::sLogModuleManager->CreateOrGetModule(aModule); + if (MOZ_LOG_TEST(m, aLevel)) { + mozilla::detail::log_print(m, aLevel, "%s", aMsg); + } +} + +} // extern "C" diff --git a/xpcom/base/Logging.h b/xpcom/base/Logging.h new file mode 100644 index 0000000000..6ed2752315 --- /dev/null +++ b/xpcom/base/Logging.h @@ -0,0 +1,322 @@ +/* -*- 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 <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> + +#include "mozilla/Assertions.h" +#include "mozilla/Atomics.h" +#include "mozilla/Attributes.h" +#include "mozilla/Likely.h" + +// 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 + +// The mandatory extension we add to log files. Note that rotate will append +// the file piece number still at the end. +#define MOZ_LOG_FILE_EXTENSION ".moz_log" + +// Token for Process ID substitution. +#define MOZ_LOG_PID_TOKEN "%PID" + +namespace mozilla { + +class TimeStamp; + +// 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); + + /** + * Logging processes -MOZ_LOG and -MOZ_LOG_FILE command line arguments + * to override or set modules and the file as if passed through MOZ_LOG + * and MOZ_LOG_FILE env vars. It's fine to pass (0, nullptr) if args + * are not accessible in the caller's context, it will just do nothing. + * Note that the args take effect (are processed) only when this function + * is called the first time. + */ + static void Init(int argc, char* argv[]); + + /** + * 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); + + /** + * @param aCaptureStacks If we should capture stacks for the Firefox + * Profiler markers that are recorded for for each log entry. + */ + static void SetCaptureStacks(bool aCaptureStacks); + + /** + * 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); + + /** + * Print a log message for this module. + */ + void Printv(LogLevel aLevel, const char* aFmt, va_list aArgs) const + MOZ_FORMAT_PRINTF(3, 0); + + void Printv(LogLevel aLevel, const TimeStamp* aStart, const char* aFmt, + va_list aArgs) const MOZ_FORMAT_PRINTF(4, 0); + + /** + * 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) {} + + MOZ_NEVER_INLINE_DEBUG 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 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, ...) + MOZ_FORMAT_PRINTF(3, 4); + +void log_print(const LogModule* aModule, LogLevel aLevel, TimeStamp* aStart, + const char* aFmt, ...) MOZ_FORMAT_PRINTF(4, 5); +} // 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) \ + MOZ_UNLIKELY(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... +// } +# define MOZ_LOG_TEST(_module, _level) false +#endif + +// The natural definition of the MOZ_LOG macro would expand to: +// +// do { +// if (MOZ_LOG_TEST(_module, _level)) { +// mozilla::detail::log_print(_module, ...); +// } +// } while (0) +// +// However, since _module is a LazyLogModule, and we need to call +// LazyLogModule::operator() to get a LogModule* for the MOZ_LOG_TEST +// macro and for the logging call, we'll wind up doing *two* calls, one +// for each, rather than a single call. The compiler is not able to +// fold the two calls into one, and the extra call can have a +// significant effect on code size. (Making LazyLogModule::operator() a +// `const` function does not have any effect.) +// +// Therefore, we will have to make a single call ourselves. But again, +// the natural definition: +// +// do { +// ::mozilla::LogModule* real_module = _module; +// if (MOZ_LOG_TEST(real_module, _level)) { +// mozilla::detail::log_print(real_module, ...); +// } +// } while (0) +// +// also has a problem: if logging is disabled, then we will call +// LazyLogModule::operator() unnecessarily, and the compiler will not be +// able to optimize away the call as dead code. We would like to avoid +// such a scenario, as the whole point of disabling logging is for the +// logging statements to not generate any code. +// +// Therefore, we need different definitions of MOZ_LOG, depending on +// whether logging is enabled or not. (We need an actual definition of +// MOZ_LOG even when logging is disabled to ensure the compiler sees that +// variables only used during logging code are actually used, even if the +// code will never be executed.) Hence, the following code. +// +// MOZ_LOG_DURATION takes a start time, and will generate a time range +// in the logs. Also, if the Firefox Profiler is running, +// MOZ_LOG_DURATION will generate a marker with a time duration +// instead of a single point in time. +#if MOZ_LOGGING_ENABLED +# define MOZ_LOG(_module, _level, _args) \ + do { \ + const ::mozilla::LogModule* moz_real_module = _module; \ + if (MOZ_LOG_TEST(moz_real_module, _level)) { \ + mozilla::detail::log_print(moz_real_module, _level, \ + MOZ_LOG_EXPAND_ARGS _args); \ + } \ + } while (0) +# define MOZ_LOG_DURATION(_module, _level, start, _args) \ + do { \ + const ::mozilla::LogModule* moz_real_module = _module; \ + if (MOZ_LOG_TEST(moz_real_module, _level)) { \ + mozilla::detail::log_print(moz_real_module, _level, start, \ + MOZ_LOG_EXPAND_ARGS _args); \ + } \ + } while (0) +#else +# define MOZ_LOG(_module, _level, _args) \ + do { \ + if (MOZ_LOG_TEST(_module, _level)) { \ + mozilla::detail::log_print(_module, _level, \ + MOZ_LOG_EXPAND_ARGS _args); \ + } \ + } while (0) +# define MOZ_LOG_DURATION(_module, _level, start, _args) \ + do { \ + if (MOZ_LOG_TEST(_module, _level)) { \ + mozilla::detail::log_print(_module, _level, start, \ + MOZ_LOG_EXPAND_ARGS _args); \ + } \ + } while (0) +#endif + +// 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 0000000000..baf4321034 --- /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 0000000000..26c04c49dd --- /dev/null +++ b/xpcom/base/MacHelpers.mm @@ -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/. */ + +#include "nsString.h" +#include "MacHelpers.h" +#include "MacStringHelpers.h" +#include "nsObjCExceptions.h" + +#import <Foundation/Foundation.h> + +namespace mozilla { + +nsresult GetSelectedCityInfo(nsAString& aCountryCode) { + NS_OBJC_BEGIN_TRY_BLOCK_RETURN; + + // 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; + } + + return mozilla::CopyCocoaStringToXPCOMString((NSString*)countryCode, + aCountryCode); + + NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE); +} + +} // namespace mozilla diff --git a/xpcom/base/MacStringHelpers.h b/xpcom/base/MacStringHelpers.h new file mode 100644 index 0000000000..c2f9ee82dc --- /dev/null +++ b/xpcom/base/MacStringHelpers.h @@ -0,0 +1,20 @@ +/* -*- 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_MacStringHelpers_h +#define mozilla_MacStringHelpers_h + +#include "nsString.h" + +#import <Foundation/Foundation.h> + +namespace mozilla { + +nsresult CopyCocoaStringToXPCOMString(NSString* aFrom, nsAString& aTo); + +} // namespace mozilla + +#endif diff --git a/xpcom/base/MacStringHelpers.mm b/xpcom/base/MacStringHelpers.mm new file mode 100644 index 0000000000..cf0b03665d --- /dev/null +++ b/xpcom/base/MacStringHelpers.mm @@ -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/. */ + +#include "MacStringHelpers.h" +#include "nsObjCExceptions.h" + +#include "mozilla/IntegerTypeTraits.h" +#include <limits> + +namespace mozilla { + +nsresult CopyCocoaStringToXPCOMString(NSString* aFrom, nsAString& aTo) { + NS_OBJC_BEGIN_TRY_BLOCK_RETURN; + + NSUInteger len = [aFrom length]; + if (len > std::numeric_limits<nsAString::size_type>::max()) { + return NS_ERROR_OUT_OF_MEMORY; + } + + if (!aTo.SetLength(len, mozilla::fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + [aFrom getCharacters:reinterpret_cast<unichar*>(aTo.BeginWriting()) + range:NSMakeRange(0, len)]; + + return NS_OK; + + NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE); +} + +} // namespace mozilla diff --git a/xpcom/base/MemoryInfo.cpp b/xpcom/base/MemoryInfo.cpp new file mode 100644 index 0000000000..6ce7f2b768 --- /dev/null +++ b/xpcom/base/MemoryInfo.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 "mozilla/MemoryInfo.h" + +#include "mozilla/DebugOnly.h" + +#include <algorithm> +#include <windows.h> + +namespace mozilla { + +/* static */ +MemoryInfo MemoryInfo::Get(const void* aPtr, size_t aSize) { + MemoryInfo result; + + result.mStart = uintptr_t(aPtr); + const char* ptr = reinterpret_cast<const char*>(aPtr); + const char* end = ptr + aSize; + DebugOnly<void*> base = nullptr; + while (ptr < end) { + MEMORY_BASIC_INFORMATION basicInfo; + if (!VirtualQuery(ptr, &basicInfo, sizeof(basicInfo))) { + break; + } + + MOZ_ASSERT_IF(base, base == basicInfo.AllocationBase); + base = basicInfo.AllocationBase; + + size_t regionSize = + std::min(size_t(basicInfo.RegionSize), size_t(end - ptr)); + + if (basicInfo.State == MEM_COMMIT) { + result.mCommitted += regionSize; + } else if (basicInfo.State == MEM_RESERVE) { + result.mReserved += regionSize; + } else if (basicInfo.State == MEM_FREE) { + result.mFree += regionSize; + } else { + MOZ_ASSERT_UNREACHABLE("Unexpected region state"); + } + result.mSize += regionSize; + ptr += regionSize; + + if (result.mType.isEmpty()) { + if (basicInfo.Type & MEM_IMAGE) { + result.mType += PageType::Image; + } + if (basicInfo.Type & MEM_MAPPED) { + result.mType += PageType::Mapped; + } + if (basicInfo.Type & MEM_PRIVATE) { + result.mType += PageType::Private; + } + + // The first 8 bits of AllocationProtect are an enum. The remaining bits + // are flags. + switch (basicInfo.AllocationProtect & 0xff) { + case PAGE_EXECUTE_WRITECOPY: + result.mPerms += Perm::CopyOnWrite; + [[fallthrough]]; + case PAGE_EXECUTE_READWRITE: + result.mPerms += Perm::Write; + [[fallthrough]]; + case PAGE_EXECUTE_READ: + result.mPerms += Perm::Read; + [[fallthrough]]; + case PAGE_EXECUTE: + result.mPerms += Perm::Execute; + break; + + case PAGE_WRITECOPY: + result.mPerms += Perm::CopyOnWrite; + [[fallthrough]]; + case PAGE_READWRITE: + result.mPerms += Perm::Write; + [[fallthrough]]; + case PAGE_READONLY: + result.mPerms += Perm::Read; + break; + + default: + break; + } + + if (basicInfo.AllocationProtect & PAGE_GUARD) { + result.mPerms += Perm::Guard; + } + if (basicInfo.AllocationProtect & PAGE_NOCACHE) { + result.mPerms += Perm::NoCache; + } + if (basicInfo.AllocationProtect & PAGE_WRITECOMBINE) { + result.mPerms += Perm::WriteCombine; + } + } + } + + result.mEnd = uintptr_t(ptr); + return result; +} + +} // namespace mozilla diff --git a/xpcom/base/MemoryInfo.h b/xpcom/base/MemoryInfo.h new file mode 100644 index 0000000000..d122f6d377 --- /dev/null +++ b/xpcom/base/MemoryInfo.h @@ -0,0 +1,81 @@ +/* -*- 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_MemoryInfo_h +#define mozilla_MemoryInfo_h + +#include <cstddef> +#include <cstdint> +#include "mozilla/Attributes.h" +#include "mozilla/EnumSet.h" +/** + * MemoryInfo is a helper class which describes the attributes and sizes of a + * particular region of VM memory on Windows. It roughtly corresponds to the + * values in a MEMORY_BASIC_INFORMATION struct, summed over an entire region or + * memory. + */ + +namespace mozilla { + +class MemoryInfo final { + public: + enum class Perm : uint8_t { + Read, + Write, + Execute, + CopyOnWrite, + Guard, + NoCache, + WriteCombine, + }; + enum class PageType : uint8_t { + Image, + Mapped, + Private, + }; + + using PermSet = EnumSet<Perm>; + using PageTypeSet = EnumSet<PageType>; + + MemoryInfo() = default; + MOZ_IMPLICIT MemoryInfo(const MemoryInfo&) = default; + + uintptr_t Start() const { return mStart; } + uintptr_t End() const { return mEnd; } + + PageTypeSet Type() const { return mType; } + PermSet Perms() const { return mPerms; } + + size_t Reserved() const { return mReserved; } + size_t Committed() const { return mCommitted; } + size_t Free() const { return mFree; } + size_t Size() const { return mSize; } + + // Returns a MemoryInfo object containing the sums of all region sizes, + // divided into Reserved, Committed, and Free, depending on their State + // properties. + // + // The entire range of aSize bytes starting at aPtr must correspond to a + // single allocation. This restriction is enforced in debug builds. + static MemoryInfo Get(const void* aPtr, size_t aSize); + + private: + uintptr_t mStart = 0; + uintptr_t mEnd = 0; + + size_t mReserved = 0; + size_t mCommitted = 0; + size_t mFree = 0; + size_t mSize = 0; + + PageTypeSet mType{}; + + PermSet mPerms{}; +}; + +} // namespace mozilla + +#endif // mozilla_MemoryInfo_h diff --git a/xpcom/base/MemoryMapping.cpp b/xpcom/base/MemoryMapping.cpp new file mode 100644 index 0000000000..3a91d7ceae --- /dev/null +++ b/xpcom/base/MemoryMapping.cpp @@ -0,0 +1,207 @@ +/* -*- 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/MemoryMapping.h" + +#include "mozilla/BinarySearch.h" +#include "mozilla/FileUtils.h" + +#include <fstream> +#include <string> +#include <sstream> + +namespace mozilla { + +namespace { +struct VMFlagString { + const char* mName; + const char* mPrettyName; + VMFlag mFlag; +}; + +static const VMFlagString sVMFlagStrings[] = { + // clang-format off + {"ac", "Accountable", VMFlag::Accountable}, + {"ar", "ArchSpecific", VMFlag::ArchSpecific}, + {"dc", "NoFork", VMFlag::NoFork}, + {"dd", "NoCore", VMFlag::NoCore}, + {"de", "NoExpand", VMFlag::NoExpand}, + {"dw", "DisabledWrite", VMFlag::DisabledWrite}, + {"ex", "Executable", VMFlag::Executable}, + {"gd", "GrowsDown", VMFlag::GrowsDown}, + {"hg", "HugePage", VMFlag::HugePage}, + {"ht", "HugeTLB", VMFlag::HugeTLB}, + {"io", "IO", VMFlag::IO}, + {"lo", "Locked", VMFlag::Locked}, + {"me", "MayExecute", VMFlag::MayExecute}, + {"mg", "Mergeable", VMFlag::Mergeable}, + {"mm", "MixedMap", VMFlag::MixedMap}, + {"mr", "MayRead", VMFlag::MayRead}, + {"ms", "MayShare", VMFlag::MayShare}, + {"mw", "MayWrite", VMFlag::MayWrite}, + {"nh", "NoHugePage", VMFlag::NoHugePage}, + {"nl", "NonLinear", VMFlag::NonLinear}, + {"nr", "NotReserved", VMFlag::NotReserved}, + {"pf", "PurePFN", VMFlag::PurePFN}, + {"rd", "Readable", VMFlag::Readable}, + {"rr", "Random", VMFlag::Random}, + {"sd", "SoftDirty", VMFlag::SoftDirty}, + {"sh", "Shared", VMFlag::Shared}, + {"sr", "Sequential", VMFlag::Sequential}, + {"wr", "Writable", VMFlag::Writable}, + // clang-format on +}; +} // anonymous namespace + +constexpr size_t kVMFlags = size_t(-1); + +// An array of known field names which may be present in an smaps file, and the +// offsets of the corresponding fields in a MemoryMapping class. +const MemoryMapping::Field MemoryMapping::sFields[] = { + // clang-format off + {"AnonHugePages", offsetof(MemoryMapping, mAnonHugePages)}, + {"Anonymous", offsetof(MemoryMapping, mAnonymous)}, + {"KernelPageSize", offsetof(MemoryMapping, mKernelPageSize)}, + {"LazyFree", offsetof(MemoryMapping, mLazyFree)}, + {"Locked", offsetof(MemoryMapping, mLocked)}, + {"MMUPageSize", offsetof(MemoryMapping, mMMUPageSize)}, + {"Private_Clean", offsetof(MemoryMapping, mPrivate_Clean)}, + {"Private_Dirty", offsetof(MemoryMapping, mPrivate_Dirty)}, + {"Private_Hugetlb", offsetof(MemoryMapping, mPrivate_Hugetlb)}, + {"Pss", offsetof(MemoryMapping, mPss)}, + {"Referenced", offsetof(MemoryMapping, mReferenced)}, + {"Rss", offsetof(MemoryMapping, mRss)}, + {"Shared_Clean", offsetof(MemoryMapping, mShared_Clean)}, + {"Shared_Dirty", offsetof(MemoryMapping, mShared_Dirty)}, + {"Shared_Hugetlb", offsetof(MemoryMapping, mShared_Hugetlb)}, + {"ShmemPmdMapped", offsetof(MemoryMapping, mShmemPmdMapped)}, + {"Size", offsetof(MemoryMapping, mSize)}, + {"Swap", offsetof(MemoryMapping, mSwap)}, + {"SwapPss", offsetof(MemoryMapping, mSwapPss)}, + // VmFlags is a special case. It contains an array of flag strings, which + // describe attributes of the mapping, rather than a mapping size. We include + // it in this array to aid in parsing, but give it a separate sentinel value, + // and treat it specially. + {"VmFlags", kVMFlags}, + // clang-format on +}; + +template <typename T, int n> +const T* FindEntry(const char* aName, const T (&aEntries)[n]) { + size_t index; + if (BinarySearchIf( + aEntries, 0, n, + [&](const T& aEntry) { return strcmp(aName, aEntry.mName); }, + &index)) { + return &aEntries[index]; + } + return nullptr; +} + +using Perm = MemoryMapping::Perm; +using PermSet = MemoryMapping::PermSet; + +nsresult GetMemoryMappings(nsTArray<MemoryMapping>& aMappings, pid_t aPid) { + std::ifstream stream; + if (aPid == 0) { + stream.open("/proc/self/smaps"); + } else { + std::ostringstream path; + path << "/proc/" << aPid << "/smaps" << std::ends; + stream.open(path.str()); + } + if (stream.fail()) { + return NS_ERROR_FAILURE; + } + + MemoryMapping* current = nullptr; + std::string line; + while (std::getline(stream, line)) { + size_t start, end, offset; + char flags[4] = "---"; + char name[512]; + + name[0] = 0; + + // clang-format off + // Match the start of an entry. A typical line looks something like: + // + // 1487118a7000-148711a5a000 r-xp 00000000 103:03 54004561 /usr/lib/libc-2.27.so + // clang-format on + if (sscanf(line.c_str(), "%zx-%zx %4c %zx %*u:%*u %*u %511s\n", &start, + &end, flags, &offset, name) >= 4) { + PermSet perms; + if (flags[0] == 'r') { + perms += Perm::Read; + } + if (flags[1] == 'w') { + perms += Perm::Write; + } + if (flags[2] == 'x') { + perms += Perm::Execute; + } + if (flags[3] == 'p') { + perms += Perm::Private; + } else if (flags[3] == 's') { + perms += Perm::Shared; + } + + current = aMappings.AppendElement( + MemoryMapping{start, end, perms, offset, name}); + continue; + } + if (!current) { + continue; + } + + char* savePtr; + char* fieldName = strtok_r(line.data(), ":", &savePtr); + if (!fieldName) { + continue; + } + auto* field = FindEntry(fieldName, MemoryMapping::sFields); + if (!field) { + continue; + } + + if (field->mOffset == kVMFlags) { + while (char* flagName = strtok_r(nullptr, " \n", &savePtr)) { + if (auto* flag = FindEntry(flagName, sVMFlagStrings)) { + current->mFlags += flag->mFlag; + } + } + continue; + } + + const char* rest = strtok_r(nullptr, "\n", &savePtr); + size_t value; + if (sscanf(rest, "%zd kB", &value) > 0) { + current->ValueForField(*field) = value * 1024; + } + } + + return NS_OK; +} + +void MemoryMapping::Dump(nsACString& aOut) const { + aOut.AppendPrintf("%zx-%zx Size: %zu Offset: %zx %s\n", mStart, mEnd, + mEnd - mStart, mOffset, mName.get()); + + for (auto& field : MemoryMapping::sFields) { + if (field.mOffset < sizeof(*this)) { + aOut.AppendPrintf(" %s: %zd\n", field.mName, ValueForField(field)); + } + } + + aOut.AppendPrintf(" Flags: %x\n", mFlags.serialize()); + for (auto& flag : sVMFlagStrings) { + if (mFlags.contains(flag.mFlag)) { + aOut.AppendPrintf(" : %s %s\n", flag.mName, flag.mPrettyName); + } + } +} + +} // namespace mozilla diff --git a/xpcom/base/MemoryMapping.h b/xpcom/base/MemoryMapping.h new file mode 100644 index 0000000000..51bbb0ab66 --- /dev/null +++ b/xpcom/base/MemoryMapping.h @@ -0,0 +1,183 @@ +/* -*- 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_MemoryMapping_h +#define mozilla_MemoryMapping_h + +#include <cstdint> +#include "mozilla/EnumSet.h" +#include "nsString.h" +#include "nsTArrayForwardDeclare.h" + +/** + * MemoryMapping is a helper class which describes an entry in the Linux + * /proc/<pid>/smaps file. See procfs(5) for details on the entry format. + * + * The GetMemoryMappings() function returns an array of such entries, sorted by + * start address, one for each entry in the current process's address space. + */ + +namespace mozilla { + +enum class VMFlag : uint8_t { + Readable, // rd - readable + Writable, // wr - writable + Executable, // ex - executable + Shared, // sh - shared + MayRead, // mr - may read + MayWrite, // mw - may write + MayExecute, // me - may execute + MayShare, // ms - may share + GrowsDown, // gd - stack segment grows down + PurePFN, // pf - pure PFN range + DisabledWrite, // dw - disabled write to the mapped file + Locked, // lo - pages are locked in memory + IO, // io - memory mapped I/O area + Sequential, // sr - sequential read advise provided + Random, // rr - random read advise provided + NoFork, // dc - do not copy area on fork + NoExpand, // de - do not expand area on remapping + Accountable, // ac - area is accountable + NotReserved, // nr - swap space is not reserved for the area + HugeTLB, // ht - area uses huge tlb pages + NonLinear, // nl - non-linear mapping + ArchSpecific, // ar - architecture specific flag + NoCore, // dd - do not include area into core dump + SoftDirty, // sd - soft-dirty flag + MixedMap, // mm - mixed map area + HugePage, // hg - huge page advise flag + NoHugePage, // nh - no-huge page advise flag + Mergeable, // mg - mergeable advise flag +}; + +using VMFlagSet = EnumSet<VMFlag, uint32_t>; + +class MemoryMapping final { + public: + enum class Perm : uint8_t { + Read, + Write, + Execute, + Shared, + Private, + }; + + using PermSet = EnumSet<Perm>; + + MemoryMapping(uintptr_t aStart, uintptr_t aEnd, PermSet aPerms, + size_t aOffset, const char* aName) + : mStart(aStart), + mEnd(aEnd), + mOffset(aOffset), + mName(aName), + mPerms(aPerms) {} + + const nsCString& Name() const { return mName; } + + uintptr_t Start() const { return mStart; } + uintptr_t End() const { return mEnd; } + + bool Includes(const void* aPtr) const { + auto ptr = uintptr_t(aPtr); + return ptr >= mStart && ptr < mEnd; + } + + PermSet Perms() const { return mPerms; } + VMFlagSet VMFlags() const { return mFlags; } + + // For file mappings, the offset in the mapped file which corresponds to the + // start of the mapped region. + size_t Offset() const { return mOffset; } + + size_t AnonHugePages() const { return mAnonHugePages; } + size_t Anonymous() const { return mAnonymous; } + size_t KernelPageSize() const { return mKernelPageSize; } + size_t LazyFree() const { return mLazyFree; } + size_t Locked() const { return mLocked; } + size_t MMUPageSize() const { return mMMUPageSize; } + size_t Private_Clean() const { return mPrivate_Clean; } + size_t Private_Dirty() const { return mPrivate_Dirty; } + size_t Private_Hugetlb() const { return mPrivate_Hugetlb; } + size_t Pss() const { return mPss; } + size_t Referenced() const { return mReferenced; } + size_t Rss() const { return mRss; } + size_t Shared_Clean() const { return mShared_Clean; } + size_t Shared_Dirty() const { return mShared_Dirty; } + size_t Shared_Hugetlb() const { return mShared_Hugetlb; } + size_t ShmemPmdMapped() const { return mShmemPmdMapped; } + size_t Size() const { return mSize; } + size_t Swap() const { return mSwap; } + size_t SwapPss() const { return mSwapPss; } + + // Dumps a string representation of the entry, similar to its format in the + // smaps file, to the given string. Mainly useful for debugging. + void Dump(nsACString& aOut) const; + + // These comparison operators are used for binary searching sorted arrays of + // MemoryMapping entries to find the one which contains a given pointer. + bool operator==(const void* aPtr) const { return Includes(aPtr); } + bool operator<(const void* aPtr) const { return mStart < uintptr_t(aPtr); } + + private: + friend nsresult GetMemoryMappings(nsTArray<MemoryMapping>& aMappings, + pid_t aPid); + + uintptr_t mStart = 0; + uintptr_t mEnd = 0; + + size_t mOffset = 0; + + nsCString mName; + + // Members for size fields in the smaps file. Please keep these in sync with + // the sFields array. + size_t mAnonHugePages = 0; + size_t mAnonymous = 0; + size_t mKernelPageSize = 0; + size_t mLazyFree = 0; + size_t mLocked = 0; + size_t mMMUPageSize = 0; + size_t mPrivate_Clean = 0; + size_t mPrivate_Dirty = 0; + size_t mPrivate_Hugetlb = 0; + size_t mPss = 0; + size_t mReferenced = 0; + size_t mRss = 0; + size_t mShared_Clean = 0; + size_t mShared_Dirty = 0; + size_t mShared_Hugetlb = 0; + size_t mShmemPmdMapped = 0; + size_t mSize = 0; + size_t mSwap = 0; + size_t mSwapPss = 0; + + PermSet mPerms{}; + VMFlagSet mFlags{}; + + // Contains the name and offset of one of the above size_t fields, for use in + // parsing in dumping. The below helpers contain a list of the fields, and map + // Field entries to the appropriate member in a class instance. + struct Field { + const char* mName; + size_t mOffset; + }; + + static const Field sFields[20]; + + size_t& ValueForField(const Field& aField) { + char* fieldPtr = reinterpret_cast<char*>(this) + aField.mOffset; + return reinterpret_cast<size_t*>(fieldPtr)[0]; + } + size_t ValueForField(const Field& aField) const { + return const_cast<MemoryMapping*>(this)->ValueForField(aField); + } +}; + +nsresult GetMemoryMappings(nsTArray<MemoryMapping>& aMappings, pid_t aPid = 0); + +} // namespace mozilla + +#endif // mozilla_MemoryMapping_h diff --git a/xpcom/base/MemoryPressureLevelMac.h b/xpcom/base/MemoryPressureLevelMac.h new file mode 100644 index 0000000000..38cb49a4fd --- /dev/null +++ b/xpcom/base/MemoryPressureLevelMac.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_MemoryPressureLevelMac_h +#define mozilla_MemoryPressureLevelMac_h + +namespace mozilla { + +#if defined(XP_DARWIN) +// An internal representation of the Mac memory-pressure level constants. +class MacMemoryPressureLevel { + public: + // Order enum values so that higher integer values represent higher + // memory pressure levels allowing comparison operators to be used. + enum class Value { + eUnset, + eUnexpected, + eNormal, + eWarning, + eCritical, + }; + + MacMemoryPressureLevel() : mValue(Value::eUnset) {} + MOZ_IMPLICIT MacMemoryPressureLevel(Value aValue) : mValue(aValue) {} + + bool operator==(const Value& aRhsValue) const { return mValue == aRhsValue; } + bool operator==(const MacMemoryPressureLevel& aRhs) const { + return mValue == aRhs.mValue; + } + + // Implement '<' and derive the other comparators from it. + bool operator<(const MacMemoryPressureLevel& aRhs) const { + return mValue < aRhs.mValue; + } + bool operator>(const MacMemoryPressureLevel& aRhs) const { + return aRhs < *this; + } + bool operator<=(const MacMemoryPressureLevel& aRhs) const { + return !(aRhs < *this); + } + bool operator>=(const MacMemoryPressureLevel& aRhs) const { + return !(*this < aRhs); + } + + Value GetValue() { return mValue; } + bool IsNormal() { return mValue == Value::eNormal; } + bool IsUnsetOrNormal() { return IsNormal() || (mValue == Value::eUnset); } + bool IsWarningOrAbove() { + return (mValue == Value::eWarning) || (mValue == Value::eCritical); + } + + const char* ToString() { + switch (mValue) { + case Value::eUnset: + return "Unset"; + case Value::eUnexpected: + return "Unexpected"; + case Value::eNormal: + return "Normal"; + case Value::eWarning: + return "Warning"; + case Value::eCritical: + return "Critical"; + } + } + + private: + Value mValue; +}; +#endif + +} // namespace mozilla + +#endif // mozilla_MemoryPressureLevelMac_h diff --git a/xpcom/base/MemoryReportingProcess.h b/xpcom/base/MemoryReportingProcess.h new file mode 100644 index 0000000000..36410e702b --- /dev/null +++ b/xpcom/base/MemoryReportingProcess.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 xpcom_base_MemoryReportingProcess_h +#define xpcom_base_MemoryReportingProcess_h + +#include <stdint.h> +#include "nscore.h" + +namespace mozilla { +namespace ipc { +class FileDescriptor; +} // namespace ipc + +template <class T> +class Maybe; + +// Top-level process actors should implement this to integrate with +// nsMemoryReportManager. +class MemoryReportingProcess { + public: + NS_IMETHOD_(MozExternalRefCountType) AddRef() = 0; + NS_IMETHOD_(MozExternalRefCountType) Release() = 0; + + virtual ~MemoryReportingProcess() = default; + + // Return true if the process is still alive, false otherwise. + virtual bool IsAlive() const = 0; + + // Initiate a memory report request, returning true if a report was + // successfully initiated and false otherwise. + virtual bool SendRequestMemoryReport( + const uint32_t& aGeneration, const bool& aAnonymize, + const bool& aMinimizeMemoryUsage, + const Maybe<mozilla::ipc::FileDescriptor>& aDMDFile) = 0; + + virtual int32_t Pid() const = 0; +}; + +} // namespace mozilla + +#endif // xpcom_base_MemoryReportingProcess_h diff --git a/xpcom/base/MemoryTelemetry.cpp b/xpcom/base/MemoryTelemetry.cpp new file mode 100644 index 0000000000..d89c528049 --- /dev/null +++ b/xpcom/base/MemoryTelemetry.cpp @@ -0,0 +1,526 @@ +/* -*- 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 "MemoryTelemetry.h" +#include "nsMemoryReporterManager.h" + +#include "mozilla/ClearOnShutdown.h" +#ifdef MOZ_PHC +# include "mozilla/PHCManager.h" +#endif +#include "mozilla/Result.h" +#include "mozilla/ResultExtensions.h" +#include "mozilla/Services.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/SimpleEnumerator.h" +#include "mozilla/Telemetry.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/ScriptSettings.h" +#include "nsContentUtils.h" +#include "nsGlobalWindowOuter.h" +#include "nsIBrowserDOMWindow.h" +#include "nsIMemoryReporter.h" +#include "nsIWindowMediator.h" +#include "nsImportModule.h" +#include "nsITelemetry.h" +#include "nsNetCID.h" +#include "nsObserverService.h" +#include "nsReadableUtils.h" +#include "nsThreadUtils.h" +#include "nsXULAppAPI.h" +#include "xpcpublic.h" + +#include <cstdlib> + +using namespace mozilla; + +using mozilla::dom::AutoJSAPI; +using mozilla::dom::ContentParent; + +// Do not gather data more than once a minute (ms) +static constexpr uint32_t kTelemetryInterval = 60 * 1000; + +static constexpr const char* kTopicCycleCollectorBegin = + "cycle-collector-begin"; + +namespace { + +enum class PrevValue : uint32_t { +#ifdef XP_WIN + LOW_MEMORY_EVENTS_VIRTUAL, + LOW_MEMORY_EVENTS_COMMIT_SPACE, + LOW_MEMORY_EVENTS_PHYSICAL, +#endif +#if defined(XP_LINUX) && !defined(ANDROID) + PAGE_FAULTS_HARD, +#endif + SIZE_, +}; + +} // anonymous namespace + +constexpr uint32_t kUninitialized = ~0; + +static uint32_t gPrevValues[uint32_t(PrevValue::SIZE_)]; + +static uint32_t PrevValueIndex(Telemetry::HistogramID aId) { + switch (aId) { +#ifdef XP_WIN + case Telemetry::LOW_MEMORY_EVENTS_VIRTUAL: + return uint32_t(PrevValue::LOW_MEMORY_EVENTS_VIRTUAL); + case Telemetry::LOW_MEMORY_EVENTS_COMMIT_SPACE: + return uint32_t(PrevValue::LOW_MEMORY_EVENTS_COMMIT_SPACE); + case Telemetry::LOW_MEMORY_EVENTS_PHYSICAL: + return uint32_t(PrevValue::LOW_MEMORY_EVENTS_PHYSICAL); +#endif +#if defined(XP_LINUX) && !defined(ANDROID) + case Telemetry::PAGE_FAULTS_HARD: + return uint32_t(PrevValue::PAGE_FAULTS_HARD); +#endif + default: + MOZ_ASSERT_UNREACHABLE("Unexpected histogram ID"); + return 0; + } +} + +NS_IMPL_ISUPPORTS(MemoryTelemetry, nsIObserver, nsISupportsWeakReference) + +MemoryTelemetry::MemoryTelemetry() + : mThreadPool(do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID)) {} + +void MemoryTelemetry::Init() { + for (auto& val : gPrevValues) { + val = kUninitialized; + } + + if (XRE_IsContentProcess()) { + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + MOZ_RELEASE_ASSERT(obs); + + obs->AddObserver(this, "content-child-shutdown", true); + } +} + +/* static */ MemoryTelemetry& MemoryTelemetry::Get() { + static RefPtr<MemoryTelemetry> sInstance; + + MOZ_ASSERT(NS_IsMainThread()); + + if (!sInstance) { + sInstance = new MemoryTelemetry(); + sInstance->Init(); + ClearOnShutdown(&sInstance); + } + return *sInstance; +} + +nsresult MemoryTelemetry::DelayedInit() { + if (Telemetry::CanRecordExtended()) { + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + MOZ_RELEASE_ASSERT(obs); + + obs->AddObserver(this, kTopicCycleCollectorBegin, true); + } + + GatherReports(); + + return NS_OK; +} + +nsresult MemoryTelemetry::Shutdown() { + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + MOZ_RELEASE_ASSERT(obs); + + obs->RemoveObserver(this, kTopicCycleCollectorBegin); + + return NS_OK; +} + +static inline void HandleMemoryReport(Telemetry::HistogramID aId, + int32_t aUnits, uint64_t aAmount, + const nsCString& aKey = VoidCString()) { + uint32_t val; + switch (aUnits) { + case nsIMemoryReporter::UNITS_BYTES: + val = uint32_t(aAmount / 1024); + break; + + case nsIMemoryReporter::UNITS_PERCENTAGE: + // UNITS_PERCENTAGE amounts are 100x greater than their raw value. + val = uint32_t(aAmount / 100); + break; + + case nsIMemoryReporter::UNITS_COUNT: + val = uint32_t(aAmount); + break; + + case nsIMemoryReporter::UNITS_COUNT_CUMULATIVE: { + // If the reporter gives us a cumulative count, we'll report the + // difference in its value between now and our previous ping. + + uint32_t idx = PrevValueIndex(aId); + uint32_t prev = gPrevValues[idx]; + gPrevValues[idx] = aAmount; + + if (prev == kUninitialized) { + // If this is the first time we're reading this reporter, store its + // current value but don't report it in the telemetry ping, so we + // ignore the effect startup had on the reporter. + return; + } + val = aAmount - prev; + break; + } + + default: + MOZ_ASSERT_UNREACHABLE("Unexpected aUnits value"); + return; + } + + // Note: The reference equality check here should allow the compiler to + // optimize this case out at compile time when we weren't given a key, + // while IsEmpty() or IsVoid() most likely will not. + if (&aKey == &VoidCString()) { + Telemetry::Accumulate(aId, val); + } else { + Telemetry::Accumulate(aId, aKey, val); + } +} + +nsresult MemoryTelemetry::GatherReports( + const std::function<void()>& aCompletionCallback) { + auto cleanup = MakeScopeExit([&]() { + if (aCompletionCallback) { + aCompletionCallback(); + } + }); + + RefPtr<nsMemoryReporterManager> mgr = nsMemoryReporterManager::GetOrCreate(); + MOZ_DIAGNOSTIC_ASSERT(mgr); + NS_ENSURE_TRUE(mgr, NS_ERROR_FAILURE); + +#define RECORD(id, metric, units) \ + do { \ + int64_t amt; \ + nsresult rv = mgr->Get##metric(&amt); \ + if (NS_SUCCEEDED(rv)) { \ + HandleMemoryReport(Telemetry::id, nsIMemoryReporter::units, amt); \ + } else if (rv != NS_ERROR_NOT_AVAILABLE) { \ + NS_WARNING("Failed to retrieve memory telemetry for " #metric); \ + } \ + } while (0) + + // GHOST_WINDOWS is opt-out as of Firefox 55 + RECORD(GHOST_WINDOWS, GhostWindows, UNITS_COUNT); + + // If we're running in the parent process, collect data from all processes for + // the MEMORY_TOTAL histogram. + if (XRE_IsParentProcess() && !mGatheringTotalMemory) { + GatherTotalMemory(); + } + + if (!Telemetry::CanRecordReleaseData()) { + return NS_OK; + } + + // Get memory measurements from distinguished amount attributes. We used + // to measure "explicit" too, but it could cause hangs, and the data was + // always really noisy anyway. See bug 859657. + // + // test_TelemetrySession.js relies on some of these histograms being + // here. If you remove any of the following histograms from here, you'll + // have to modify test_TelemetrySession.js: + // + // * MEMORY_TOTAL, + // * MEMORY_JS_GC_HEAP, and + // * MEMORY_JS_COMPARTMENTS_SYSTEM. + // + // The distinguished amount attribute names don't match the telemetry id + // names in some cases due to a combination of (a) historical reasons, and + // (b) the fact that we can't change telemetry id names without breaking + // data continuity. + + // Collect cheap or main-thread only metrics synchronously, on the main + // thread. + RECORD(MEMORY_JS_GC_HEAP, JSMainRuntimeGCHeap, UNITS_BYTES); + RECORD(MEMORY_JS_COMPARTMENTS_SYSTEM, JSMainRuntimeCompartmentsSystem, + UNITS_COUNT); + RECORD(MEMORY_JS_COMPARTMENTS_USER, JSMainRuntimeCompartmentsUser, + UNITS_COUNT); + RECORD(MEMORY_JS_REALMS_SYSTEM, JSMainRuntimeRealmsSystem, UNITS_COUNT); + RECORD(MEMORY_JS_REALMS_USER, JSMainRuntimeRealmsUser, UNITS_COUNT); + RECORD(MEMORY_IMAGES_CONTENT_USED_UNCOMPRESSED, ImagesContentUsedUncompressed, + UNITS_BYTES); + RECORD(MEMORY_STORAGE_SQLITE, StorageSQLite, UNITS_BYTES); +#ifdef XP_WIN + RECORD(LOW_MEMORY_EVENTS_PHYSICAL, LowMemoryEventsPhysical, + UNITS_COUNT_CUMULATIVE); +#endif +#if defined(XP_LINUX) && !defined(ANDROID) + RECORD(PAGE_FAULTS_HARD, PageFaultsHard, UNITS_COUNT_CUMULATIVE); +#endif + +#ifdef HAVE_JEMALLOC_STATS + jemalloc_stats_t stats; + jemalloc_stats(&stats); + HandleMemoryReport(Telemetry::MEMORY_HEAP_ALLOCATED, + nsIMemoryReporter::UNITS_BYTES, mgr->HeapAllocated(stats)); + HandleMemoryReport(Telemetry::MEMORY_HEAP_OVERHEAD_FRACTION, + nsIMemoryReporter::UNITS_PERCENTAGE, + mgr->HeapOverheadFraction(stats)); +#endif + +#ifdef MOZ_PHC + ReportPHCTelemetry(); +#endif + + RefPtr<Runnable> completionRunnable; + if (aCompletionCallback) { + completionRunnable = NS_NewRunnableFunction(__func__, aCompletionCallback); + } + + // Collect expensive metrics that can be calculated off-main-thread + // asynchronously, on a background thread. + RefPtr<Runnable> runnable = NS_NewRunnableFunction( + "MemoryTelemetry::GatherReports", [mgr, completionRunnable]() mutable { + Telemetry::AutoTimer<Telemetry::MEMORY_COLLECTION_TIME> autoTimer; + RECORD(MEMORY_VSIZE, Vsize, UNITS_BYTES); +#if !defined(HAVE_64BIT_BUILD) || !defined(XP_WIN) + RECORD(MEMORY_VSIZE_MAX_CONTIGUOUS, VsizeMaxContiguous, UNITS_BYTES); +#endif + RECORD(MEMORY_RESIDENT_FAST, ResidentFast, UNITS_BYTES); + RECORD(MEMORY_RESIDENT_PEAK, ResidentPeak, UNITS_BYTES); +// Although we can measure unique memory on MacOS we choose not to, because +// doing so is too slow for telemetry. +#ifndef XP_MACOSX + RECORD(MEMORY_UNIQUE, ResidentUnique, UNITS_BYTES); +#endif + + if (completionRunnable) { + NS_DispatchToMainThread(completionRunnable.forget(), + NS_DISPATCH_NORMAL); + } + }); + +#undef RECORD + + nsresult rv = mThreadPool->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL); + if (!NS_WARN_IF(NS_FAILED(rv))) { + cleanup.release(); + } + + return NS_OK; +} + +namespace { +struct ChildProcessInfo { + GeckoProcessType mType; +#if defined(XP_WIN) + HANDLE mHandle; +#elif defined(XP_MACOSX) + task_t mHandle; +#else + pid_t mHandle; +#endif +}; +} // namespace + +/** + * Runs a task on the background thread pool to fetch the memory usage of all + * processes. + */ +void MemoryTelemetry::GatherTotalMemory() { + MOZ_ASSERT(!mGatheringTotalMemory); + mGatheringTotalMemory = true; + + nsTArray<ChildProcessInfo> infos; + mozilla::ipc::GeckoChildProcessHost::GetAll( + [&](mozilla::ipc::GeckoChildProcessHost* aGeckoProcess) { + if (!aGeckoProcess->GetChildProcessHandle()) { + return; + } + + ChildProcessInfo info{}; + info.mType = aGeckoProcess->GetProcessType(); + + // NOTE: For now we ignore non-content processes here for compatibility + // with the existing probe. We may want to introduce a new probe in the + // future which also collects data for non-content processes. + if (info.mType != GeckoProcessType_Content) { + return; + } + +#if defined(XP_WIN) + if (!::DuplicateHandle(::GetCurrentProcess(), + aGeckoProcess->GetChildProcessHandle(), + ::GetCurrentProcess(), &info.mHandle, 0, false, + DUPLICATE_SAME_ACCESS)) { + return; + } +#elif defined(XP_MACOSX) + info.mHandle = aGeckoProcess->GetChildTask(); + if (mach_port_mod_refs(mach_task_self(), info.mHandle, + MACH_PORT_RIGHT_SEND, 1) != KERN_SUCCESS) { + return; + } +#else + info.mHandle = aGeckoProcess->GetChildProcessId(); +#endif + + infos.AppendElement(info); + }); + + mThreadPool->Dispatch(NS_NewRunnableFunction( + "MemoryTelemetry::GatherTotalMemory", [infos = std::move(infos)] { + RefPtr<nsMemoryReporterManager> mgr = + nsMemoryReporterManager::GetOrCreate(); + MOZ_RELEASE_ASSERT(mgr); + + int64_t totalMemory = mgr->ResidentFast(); + nsTArray<int64_t> childSizes(infos.Length()); + + // Use our handle for the remote process to collect resident unique set + // size information for that process. + for (const auto& info : infos) { +#ifdef XP_MACOSX + int64_t memory = + nsMemoryReporterManager::PhysicalFootprint(info.mHandle); +#else + int64_t memory = + nsMemoryReporterManager::ResidentUnique(info.mHandle); +#endif + if (memory > 0) { + childSizes.AppendElement(memory); + totalMemory += memory; + } + +#if defined(XP_WIN) + ::CloseHandle(info.mHandle); +#elif defined(XP_MACOSX) + mach_port_deallocate(mach_task_self(), info.mHandle); +#endif + } + + NS_DispatchToMainThread(NS_NewRunnableFunction( + "MemoryTelemetry::FinishGatheringTotalMemory", + [totalMemory, childSizes = std::move(childSizes)] { + MemoryTelemetry::Get().FinishGatheringTotalMemory(totalMemory, + childSizes); + })); + })); +} + +nsresult MemoryTelemetry::FinishGatheringTotalMemory( + int64_t aTotalMemory, const nsTArray<int64_t>& aChildSizes) { + mGatheringTotalMemory = false; + + // Total memory usage can be difficult to measure both accurately and fast + // enough for telemetry (iterating memory maps can jank whole processes on + // MacOS). Therefore this shouldn't be relied on as an absolute measurement + // especially on MacOS where it double-counts shared memory. For a more + // detailed explaination see: + // https://groups.google.com/a/mozilla.org/g/dev-platform/c/WGNOtjHdsdA + HandleMemoryReport(Telemetry::MEMORY_TOTAL, nsIMemoryReporter::UNITS_BYTES, + aTotalMemory); + + if (aChildSizes.Length() > 1) { + int32_t tabsCount; + MOZ_TRY_VAR(tabsCount, GetOpenTabsCount()); + + nsCString key; + if (tabsCount <= 10) { + key = "0 - 10 tabs"; + } else if (tabsCount <= 500) { + key = "11 - 500 tabs"; + } else { + key = "more tabs"; + } + + // Mean of the USS of all the content processes. + int64_t mean = 0; + for (auto size : aChildSizes) { + mean += size; + } + mean /= aChildSizes.Length(); + + // For some users, for unknown reasons (though most likely because they're + // in a sandbox without procfs mounted), we wind up with 0 here, which + // triggers a floating point exception if we try to calculate values using + // it. + if (!mean) { + return NS_ERROR_UNEXPECTED; + } + + // Absolute error of USS for each content process, normalized by the mean + // (*100 to get it in percentage). 20% means for a content process that it + // is using 20% more or 20% less than the mean. + for (auto size : aChildSizes) { + int64_t diff = llabs(size - mean) * 100 / mean; + + HandleMemoryReport(Telemetry::MEMORY_DISTRIBUTION_AMONG_CONTENT, + nsIMemoryReporter::UNITS_COUNT, diff, key); + } + } + + // This notification is for testing only. + if (nsCOMPtr<nsIObserverService> obs = services::GetObserverService()) { + obs->NotifyObservers(nullptr, "gather-memory-telemetry-finished", nullptr); + } + + return NS_OK; +} + +/* static */ Result<uint32_t, nsresult> MemoryTelemetry::GetOpenTabsCount() { + nsresult rv; + + nsCOMPtr<nsIWindowMediator> windowMediator( + do_GetService(NS_WINDOWMEDIATOR_CONTRACTID, &rv)); + MOZ_TRY(rv); + + nsCOMPtr<nsISimpleEnumerator> enumerator; + MOZ_TRY(windowMediator->GetEnumerator(u"navigator:browser", + getter_AddRefs(enumerator))); + + uint32_t total = 0; + for (const auto& window : SimpleEnumerator<nsPIDOMWindowOuter>(enumerator)) { + nsCOMPtr<nsIBrowserDOMWindow> browserWin = + nsGlobalWindowOuter::Cast(window)->GetBrowserDOMWindow(); + + NS_ENSURE_TRUE(browserWin, Err(NS_ERROR_UNEXPECTED)); + + uint32_t tabCount; + MOZ_TRY(browserWin->GetTabCount(&tabCount)); + total += tabCount; + } + + return total; +} + +nsresult MemoryTelemetry::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + if (strcmp(aTopic, kTopicCycleCollectorBegin) == 0) { + auto now = TimeStamp::Now(); + if (!mLastPoll.IsNull() && + (now - mLastPoll).ToMilliseconds() < kTelemetryInterval) { + return NS_OK; + } + + mLastPoll = now; + + NS_DispatchToCurrentThreadQueue( + NewRunnableMethod<std::function<void()>>( + "MemoryTelemetry::GatherReports", this, + &MemoryTelemetry::GatherReports, nullptr), + EventQueuePriority::Idle); + } else if (strcmp(aTopic, "content-child-shutdown") == 0) { + if (nsCOMPtr<nsITelemetry> telemetry = + do_GetService("@mozilla.org/base/telemetry;1")) { + telemetry->FlushBatchedChildTelemetry(); + } + } + return NS_OK; +} diff --git a/xpcom/base/MemoryTelemetry.h b/xpcom/base/MemoryTelemetry.h new file mode 100644 index 0000000000..b7c7fe8ad6 --- /dev/null +++ b/xpcom/base/MemoryTelemetry.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 mozilla_MemoryTelemetry_h +#define mozilla_MemoryTelemetry_h + +#include "mozilla/TimeStamp.h" +#include "mozilla/Result.h" +#include "nsIObserver.h" +#include "nsITimer.h" +#include "nsTArray.h" +#include "nsWeakReference.h" + +#include <functional> + +class nsIEventTarget; + +namespace mozilla { + +namespace ipc { +enum class ResponseRejectReason; +} + +/** + * Periodically gathers memory usage metrics after cycle collection, and + * populates telemetry histograms with their values. + */ +class MemoryTelemetry final : public nsIObserver, + public nsSupportsWeakReference { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + static MemoryTelemetry& Get(); + + nsresult GatherReports( + const std::function<void()>& aCompletionCallback = nullptr); + + /** + * Does expensive initialization, which should happen only after startup has + * completed, and the event loop is idle. + */ + nsresult DelayedInit(); + + nsresult Shutdown(); + + private: + MemoryTelemetry(); + + ~MemoryTelemetry() = default; + + void Init(); + + static Result<uint32_t, nsresult> GetOpenTabsCount(); + + void GatherTotalMemory(); + nsresult FinishGatheringTotalMemory(int64_t aTotalMemory, + const nsTArray<int64_t>& aChildSizes); + + nsCOMPtr<nsIEventTarget> mThreadPool; + + bool mGatheringTotalMemory = false; + + TimeStamp mLastPoll{}; +}; + +} // namespace mozilla + +#endif // defined mozilla_MemoryTelemetry_h diff --git a/xpcom/base/NSPRLogModulesParser.cpp b/xpcom/base/NSPRLogModulesParser.cpp new file mode 100644 index 0000000000..44fb50dbc7 --- /dev/null +++ b/xpcom/base/NSPRLogModulesParser.cpp @@ -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/. */ + +#include "NSPRLogModulesParser.h" + +#include "mozilla/Tokenizer.h" + +const char kDelimiters[] = ", "; +const char kAdditionalWordChars[] = "_-.*"; + +namespace mozilla { + +void NSPRLogModulesParser( + const char* aLogModules, + const std::function<void(const char*, LogLevel, int32_t)>& aCallback) { + if (!aLogModules) { + return; + } + + Tokenizer parser(aLogModules, kDelimiters, kAdditionalWordChars); + nsAutoCString moduleName; + + Tokenizer::Token rustModSep = + parser.AddCustomToken("::", Tokenizer::CASE_SENSITIVE); + + auto readModuleName = [&](nsAutoCString& moduleName) -> bool { + moduleName.Truncate(); + nsDependentCSubstring sub; + parser.Record(); + // If the name doesn't include at least one word, we've reached the end. + if (!parser.ReadWord(sub)) { + return false; + } + // We will exit this loop if when any of the condition fails + while (parser.Check(rustModSep) && parser.ReadWord(sub)) { + } + // Claim will include characters of the last sucessfully read item + parser.Claim(moduleName, Tokenizer::INCLUDE_LAST); + return true; + }; + + // Format: LOG_MODULES="Foo:2,Bar, Baz:5,rust_crate::mod::file:5" + while (readModuleName(moduleName)) { + // Next should be :<level>, default to Error if not provided. + LogLevel logLevel = LogLevel::Error; + int32_t levelValue = 0; + if (parser.CheckChar(':')) { + // 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.ReadSignedInteger(&levelValue)) { + logLevel = ToLogLevel(levelValue); + } + } + + 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 0000000000..2aa6571e65 --- /dev/null +++ b/xpcom/base/NSPRLogModulesParser.h @@ -0,0 +1,24 @@ +/* -*- 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 <functional> + +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, + const std::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 0000000000..24c22f8f60 --- /dev/null +++ b/xpcom/base/OwningNonNull.h @@ -0,0 +1,213 @@ +/* -*- 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 "nsCOMPtr.h" +#include "nsCycleCollectionNoteChild.h" + +namespace mozilla { + +// OwningNonNull<T> is similar to a RefPtr<T>, which is not null after initial +// initialization. It has a restricted interface compared to RefPtr, with some +// additional operations defined. The main use is in DOM bindings. Use it +// outside DOM bindings only if you can ensure it never escapes without being +// properly initialized, and you don't need to move it. Otherwise, use a +// RefPtr<T> instead. +// +// Compared to a plain RefPtr<T>, in particular +// - it is copyable but not movable +// - it can be constructed and assigned from T& and is convertible to T& +// implicitly +// - it cannot be cleared by the user once initialized, though it can be +// re-assigned a new (non-null) value +// - it is not convertible to bool, but there is an explicit isInitialized +// member function +// +// Beware that there are two cases where an OwningNonNull<T> actually is nullptr +// - it was default-constructed and not yet initialized +// - it was cleared during CC unlinking. +// All attempts to use it in an invalid state will trigger an assertion in debug +// builds. +// +// The original intent of OwningNonNull<T> was to implement a class with the +// same auto-conversion and annotation semantics as mozilla::dom::NonNull<T> +// (i.e. never null once you have properly initialized it, auto-converts to T&), +// but that holds a strong reference to the object involved. This was designed +// for use in DOM bindings and in particular for storing what WebIDL represents +// as InterfaceName (as opposed to `InterfaceName?`) in various containers +// (dictionaries, sequences). DOM bindings never allow a default-constructed +// uninitialized OwningNonNull to escape. RefPtr could have been used for this +// use case, just like we could have used T* instead of NonNull<T>, but it +// seemed desirable to explicitly annotate the non-null nature of the things +// involved to eliminate pointless null-checks, which otherwise tend to +// proliferate. +template <class T> +class MOZ_IS_SMARTPTR_TO_REFCOUNTED OwningNonNull { + public: + using element_type = T; + + OwningNonNull() = default; + + 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(RefPtr<U>&& aValue) { + init(std::move(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 { return ref(); } + + operator T*() const { return get(); } + + // Conversion to bool is always true, so delete to catch errors + explicit operator bool() const = delete; + + T* operator->() const { return get(); } + + T& operator*() const { return ref(); } + + 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=(RefPtr<U>&& aValue) { + init(std::move(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; + + T& ref() const { + MOZ_ASSERT(mInited); + MOZ_ASSERT(mPtr, "OwningNonNull<T> was set to null"); + return *mPtr; + } + + // Make us work with smart pointer helpers that expect a get(). + T* get() const { + MOZ_ASSERT(mInited); + MOZ_ASSERT(mPtr, "OwningNonNull<T> was set to null"); + 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; + } + + private: + void unlinkForCC() { +#ifdef DEBUG + mInited = false; +#endif + mPtr = nullptr; + } + + // Allow ImplCycleCollectionUnlink to call unlinkForCC(). + template <typename U> + friend void ImplCycleCollectionUnlink(OwningNonNull<U>& aField); + + protected: + template <typename U> + void init(U&& aValue) { + mPtr = std::move(aValue); + MOZ_ASSERT(mPtr); +#ifdef DEBUG + mInited = true; +#endif + } + + RefPtr<T> mPtr; +#ifdef DEBUG + bool mInited = false; +#endif +}; + +template <typename T> +inline void ImplCycleCollectionUnlink(OwningNonNull<T>& aField) { + aField.unlinkForCC(); +} + +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/PHCManager.cpp b/xpcom/base/PHCManager.cpp new file mode 100644 index 0000000000..f8124312f5 --- /dev/null +++ b/xpcom/base/PHCManager.cpp @@ -0,0 +1,89 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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 "PHCManager.h" + +#include "PHC.h" +#include "mozilla/Literals.h" +#include "mozilla/Preferences.h" +#include "mozilla/StaticPrefs_memory.h" +#include "mozilla/Telemetry.h" +#include "prsystem.h" + +namespace mozilla { + +using namespace phc; + +static const char kPHCEnabledPref[] = "memory.phc.enabled"; +static const char kPHCMinRamMBPref[] = "memory.phc.min_ram_mb"; +static const char kPHCAvgDelayFirst[] = "memory.phc.avg_delay.first"; +static const char kPHCAvgDelayNormal[] = "memory.phc.avg_delay.normal"; +static const char kPHCAvgDelayPageRuse[] = "memory.phc.avg_delay.page_reuse"; + +// True if PHC has ever been enabled for this process. +static bool sWasPHCEnabled = false; + +static void UpdatePHCState() { + size_t mem_size = PR_GetPhysicalMemorySize() / (1_MiB); + size_t min_mem_size = StaticPrefs::memory_phc_min_ram_mb(); + + // Only enable PHC if there are at least 8GB of ram. Note that we use + // 1000 bytes per kilobyte rather than 1024. Some 8GB machines will have + // slightly lower actual RAM available after some hardware devices + // reserve some. + if (StaticPrefs::memory_phc_enabled() && mem_size >= min_mem_size) { + // Set PHC probablities before enabling PHC so that the first allocation + // delay gets used. + SetPHCProbabilities(StaticPrefs::memory_phc_avg_delay_first(), + StaticPrefs::memory_phc_avg_delay_normal(), + StaticPrefs::memory_phc_avg_delay_page_reuse()); + + SetPHCState(Enabled); + sWasPHCEnabled = true; + } else { + SetPHCState(OnlyFree); + } +} + +static void PrefChangeCallback(const char* aPrefName, void* aNull) { + MOZ_ASSERT((0 == strcmp(aPrefName, kPHCEnabledPref)) || + (0 == strcmp(aPrefName, kPHCMinRamMBPref)) || + (0 == strcmp(aPrefName, kPHCAvgDelayFirst)) || + (0 == strcmp(aPrefName, kPHCAvgDelayNormal)) || + (0 == strcmp(aPrefName, kPHCAvgDelayPageRuse))); + + UpdatePHCState(); +} + +void InitPHCState() { + Preferences::RegisterCallback(PrefChangeCallback, kPHCEnabledPref); + Preferences::RegisterCallback(PrefChangeCallback, kPHCMinRamMBPref); + Preferences::RegisterCallback(PrefChangeCallback, kPHCAvgDelayFirst); + Preferences::RegisterCallback(PrefChangeCallback, kPHCAvgDelayNormal); + Preferences::RegisterCallback(PrefChangeCallback, kPHCAvgDelayPageRuse); + UpdatePHCState(); +} + +void ReportPHCTelemetry() { + if (!sWasPHCEnabled) { + return; + } + + MemoryUsage usage; + PHCMemoryUsage(usage); + + Accumulate(Telemetry::MEMORY_PHC_SLOP, usage.mFragmentationBytes); + + PHCStats stats; + GetPHCStats(stats); + + Accumulate(Telemetry::MEMORY_PHC_SLOTS_ALLOCATED, stats.mSlotsAllocated); + Accumulate(Telemetry::MEMORY_PHC_SLOTS_FREED, stats.mSlotsFreed); + // There are also slots that are unused (neither free nor allocated) they + // can be calculated by knowing the total number of slots. +} + +}; // namespace mozilla diff --git a/xpcom/base/PHCManager.h b/xpcom/base/PHCManager.h new file mode 100644 index 0000000000..f1982c54eb --- /dev/null +++ b/xpcom/base/PHCManager.h @@ -0,0 +1,20 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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_PHCManager_h +#define mozilla_PHCManager_h + +namespace mozilla { + +// Read the PHC pref and potentially initialise PHC. Also register a +// callback for the pref to update PHC as the pref changes. +void InitPHCState(); + +void ReportPHCTelemetry(); + +}; // namespace mozilla + +#endif // mozilla_PHCManager_h diff --git a/xpcom/base/RLBoxSandboxPool.cpp b/xpcom/base/RLBoxSandboxPool.cpp new file mode 100644 index 0000000000..c3b86f1698 --- /dev/null +++ b/xpcom/base/RLBoxSandboxPool.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 "nsThreadUtils.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/RLBoxSandboxPool.h" +#ifdef MOZ_USING_WASM_SANDBOXING +# include "wasm2c_rt_mem.h" +#endif + +using namespace mozilla; + +NS_IMPL_ISUPPORTS(RLBoxSandboxPool, nsITimerCallback, nsINamed) + +void RLBoxSandboxPool::StartTimer() { + mMutex.AssertCurrentThreadOwns(); + MOZ_ASSERT(!mTimer, "timer already initialized"); + if (NS_IsMainThread() && + PastShutdownPhase(ShutdownPhase::AppShutdownConfirmed)) { + // If we're shutting down, setting the time might fail, and we don't need it + // (since all the memory will be cleaned up soon anyway). Note that + // PastShutdownPhase() can only be called on the main thread, but that's + // fine, because other threads will have joined already by the point timers + // start failing to register. + mPool.Clear(); + return; + } + DebugOnly<nsresult> rv = NS_NewTimerWithCallback( + getter_AddRefs(mTimer), this, mDelaySeconds * 1000, + nsITimer::TYPE_ONE_SHOT, GetMainThreadSerialEventTarget()); + MOZ_ASSERT(NS_SUCCEEDED(rv), "failed to create timer"); +} + +void RLBoxSandboxPool::CancelTimer() { + mMutex.AssertCurrentThreadOwns(); + if (mTimer) { + mTimer->Cancel(); + mTimer = nullptr; + } +} + +NS_IMETHODIMP RLBoxSandboxPool::Notify(nsITimer* aTimer) { + MutexAutoLock lock(mMutex); + + mPool.Clear(); + mTimer = nullptr; + + return NS_OK; +} + +NS_IMETHODIMP RLBoxSandboxPool::GetName(nsACString& aName) { + aName.AssignLiteral("RLBoxSandboxPool"); + return NS_OK; +} + +void RLBoxSandboxPool::Push(UniquePtr<RLBoxSandboxDataBase> sbxData) { + MutexAutoLock lock(mMutex); + + mPool.AppendElement(std::move(sbxData)); + if (!mTimer) { + StartTimer(); + } +} + +UniquePtr<RLBoxSandboxPoolData> RLBoxSandboxPool::PopOrCreate( + uint64_t aMinSize) { + MutexAutoLock lock(mMutex); + + UniquePtr<RLBoxSandboxDataBase> sbxData; + + if (!mPool.IsEmpty()) { + const int64_t lastIndex = ReleaseAssertedCast<int64_t>(mPool.Length()) - 1; + for (int64_t i = lastIndex; i >= 0; i--) { + if (mPool[i]->mSize >= aMinSize) { + sbxData = std::move(mPool[i]); + mPool.RemoveElementAt(i); + + // If we reuse a sandbox from the pool, reset the timer to clear the + // pool + CancelTimer(); + if (!mPool.IsEmpty()) { + StartTimer(); + } + break; + } + } + } + + if (!sbxData) { +#ifdef MOZ_USING_WASM_SANDBOXING + // RLBox's wasm sandboxes have a limited platform dependent capacity. We + // track this capacity in this pool. + const w2c_mem_capacity w2c_capacity = get_valid_wasm2c_memory_capacity( + aMinSize, true /* 32-bit wasm memory*/); + const uint64_t chosenCapacity = w2c_capacity.max_size; +#else + // Note the noop sandboxes have no capacity limit. In this case we simply + // specify a value of 4gb. This is not actually enforced by the noop + // sandbox. + const uint64_t chosenCapacity = static_cast<uint64_t>(1) << 32; +#endif + sbxData = CreateSandboxData(chosenCapacity); + NS_ENSURE_TRUE(sbxData, nullptr); + } + + return MakeUnique<RLBoxSandboxPoolData>(std::move(sbxData), this); +} diff --git a/xpcom/base/RLBoxSandboxPool.h b/xpcom/base/RLBoxSandboxPool.h new file mode 100644 index 0000000000..47f3893548 --- /dev/null +++ b/xpcom/base/RLBoxSandboxPool.h @@ -0,0 +1,105 @@ +/* -*- Mode: C++; tab-width: 20; 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 SECURITY_RLBOX_SANDBOX_POOL_H_ +#define SECURITY_RLBOX_SANDBOX_POOL_H_ + +#include "nsCOMPtr.h" +#include "nsITimer.h" +#include "nsTArray.h" +#include "nsINamed.h" + +#include "mozilla/Mutex.h" +#include "mozilla/rlbox/rlbox_types.hpp" + +namespace mozilla { + +class RLBoxSandboxDataBase; +class RLBoxSandboxPoolData; + +// The RLBoxSandboxPool class is used to manage a pool of sandboxes that are +// reused -- to save sandbox creation time and memory -- and automatically +// destroyed when no longer in used. The sandbox pool is threadsafe and can be +// used to share unused sandboxes across a thread pool. +// +// Each sandbox pool manages a particular kind of sandbox (e.g., expat +// sandboxes, woff2 sandboxes, etc.); this is largely because different +// sandboxes might have different callbacks and attacker assumptions. Hence, +// RLBoxSandboxPool is intended to be subclassed for the different kinds of +// sandbox pools. Each sandbox pool class needs to implement the +// CreateSandboxData() method, which returns a pointer to a RLBoxSandboxDataBase +// object. RLBoxSandboxDataBase itself should be subclassed to implement +// sandbox-specific details. +class RLBoxSandboxPool : public nsITimerCallback, public nsINamed { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSITIMERCALLBACK + NS_DECL_NSINAMED + + RLBoxSandboxPool(size_t aDelaySeconds = 10) + : mPool(), + mDelaySeconds(aDelaySeconds), + mMutex("RLBoxSandboxPool::mMutex"){}; + + void Push(UniquePtr<RLBoxSandboxDataBase> sbx); + // PopOrCreate returns a sandbox from the pool if the pool is not empty and + // tries to mint a new one otherwise. If creating a new sandbox fails, the + // function returns a nullptr. The parameter aMinSize is the minimum size of + // the sandbox memory. + UniquePtr<RLBoxSandboxPoolData> PopOrCreate(uint64_t aMinSize = 0); + + protected: + // CreateSandboxData takes a parameter which is the size of the sandbox memory + virtual UniquePtr<RLBoxSandboxDataBase> CreateSandboxData(uint64_t aSize) = 0; + virtual ~RLBoxSandboxPool() = default; + + private: + void StartTimer() MOZ_REQUIRES(mMutex); + void CancelTimer() MOZ_REQUIRES(mMutex); + + nsTArray<UniquePtr<RLBoxSandboxDataBase>> mPool MOZ_GUARDED_BY(mMutex); + const size_t mDelaySeconds MOZ_GUARDED_BY(mMutex); + nsCOMPtr<nsITimer> mTimer MOZ_GUARDED_BY(mMutex); + mozilla::Mutex mMutex; +}; + +// The RLBoxSandboxDataBase class serves as the subclass for all sandbox data +// classes, which keep track of the RLBox sandbox and any relevant sandbox data +// (e.g., callbacks). +class RLBoxSandboxDataBase { + public: + const uint64_t mSize; + explicit RLBoxSandboxDataBase(uint64_t aSize) : mSize(aSize) {} + virtual ~RLBoxSandboxDataBase() = default; +}; + +// This class is used wrap sandbox data objects (RLBoxSandboxDataBase) when they +// are popped from sandbox pools. The wrapper destructor pushes the sandbox back +// into the pool. +class RLBoxSandboxPoolData { + public: + RLBoxSandboxPoolData(UniquePtr<RLBoxSandboxDataBase> aSbxData, + RefPtr<RLBoxSandboxPool> aPool) { + mSbxData = std::move(aSbxData); + mPool = aPool; + MOZ_COUNT_CTOR(RLBoxSandboxPoolData); + } + + RLBoxSandboxDataBase* SandboxData() const { return mSbxData.get(); }; + + ~RLBoxSandboxPoolData() { + mPool->Push(std::move(mSbxData)); + MOZ_COUNT_DTOR(RLBoxSandboxPoolData); + }; + + private: + UniquePtr<RLBoxSandboxDataBase> mSbxData; + RefPtr<RLBoxSandboxPool> mPool; +}; + +} // namespace mozilla + +#endif diff --git a/xpcom/base/RLBoxUtils.h b/xpcom/base/RLBoxUtils.h new file mode 100644 index 0000000000..4a73affb63 --- /dev/null +++ b/xpcom/base/RLBoxUtils.h @@ -0,0 +1,70 @@ +/* -*- Mode: C++; tab-width: 20; 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 SECURITY_RLBOX_UTILS_H_ +#define SECURITY_RLBOX_UTILS_H_ + +#include "mozilla/rlbox/rlbox_types.hpp" + +namespace mozilla { + +/* The RLBoxTransferBufferToSandbox class is used to copy (or directly expose in + * the noop-sandbox case) buffers into the sandbox that are automatically freed + * when the RLBoxTransferBufferToSandbox is out of scope. NOTE: The sandbox + * lifetime must outlive all of its RLBoxTransferBufferToSandbox. + */ +template <typename T, typename S> +class MOZ_STACK_CLASS RLBoxTransferBufferToSandbox { + public: + RLBoxTransferBufferToSandbox() = delete; + RLBoxTransferBufferToSandbox(rlbox::rlbox_sandbox<S>* aSandbox, const T* aBuf, + const size_t aLen) + : mSandbox(aSandbox), mCopied(false), mBuf(nullptr) { + if (aBuf) { + mBuf = rlbox::copy_memory_or_grant_access(*mSandbox, aBuf, aLen, false, + mCopied); + } + }; + ~RLBoxTransferBufferToSandbox() { + if (mCopied) { + mSandbox->free_in_sandbox(mBuf); + } + }; + rlbox::tainted<const T*, S> operator*() const { return mBuf; }; + + private: + rlbox::rlbox_sandbox<S>* mSandbox; + bool mCopied; + rlbox::tainted<const T*, S> mBuf; +}; + +/* The RLBoxAllocateInSandbox class is used to allocate data int sandbox that is + * automatically freed when the RLBoxAllocateInSandbox is out of scope. NOTE: + * The sandbox lifetime must outlive all of its RLBoxAllocateInSandbox'ations. + */ +template <typename T, typename S> +class MOZ_STACK_CLASS RLBoxAllocateInSandbox { + public: + RLBoxAllocateInSandbox() = delete; + explicit RLBoxAllocateInSandbox(rlbox::rlbox_sandbox<S>* aSandbox) + : mSandbox(aSandbox) { + mPtr = mSandbox->template malloc_in_sandbox<T>(); + }; + ~RLBoxAllocateInSandbox() { + if (mPtr) { + mSandbox->free_in_sandbox(mPtr); + } + }; + rlbox::tainted<T*, S> get() const { return mPtr; }; + + private: + rlbox::rlbox_sandbox<S>* mSandbox; + rlbox::tainted<T*, S> mPtr; +}; + +} // namespace mozilla + +#endif diff --git a/xpcom/base/ShutdownPhase.h b/xpcom/base/ShutdownPhase.h new file mode 100644 index 0000000000..ebe5ebd37e --- /dev/null +++ b/xpcom/base/ShutdownPhase.h @@ -0,0 +1,32 @@ +/* -*- 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 ShutdownPhase_h +#define ShutdownPhase_h + +namespace mozilla { + +// Must be contiguous starting at 0 +enum class ShutdownPhase { + NotInShutdown = 0, + AppShutdownConfirmed, + AppShutdownNetTeardown, + AppShutdownTeardown, + AppShutdown, + AppShutdownQM, + AppShutdownTelemetry, + XPCOMWillShutdown, + XPCOMShutdown, + XPCOMShutdownThreads, + XPCOMShutdownFinal, + CCPostLastCycleCollection, + ShutdownPhase_Length, // never pass this value + First = AppShutdownConfirmed // for iteration +}; + +} // namespace mozilla + +#endif // ShutdownPhase_h diff --git a/xpcom/base/SizeOfState.h b/xpcom/base/SizeOfState.h new file mode 100644 index 0000000000..2df0b24e93 --- /dev/null +++ b/xpcom/base/SizeOfState.h @@ -0,0 +1,67 @@ +/* -*- 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 SizeOfState_h +#define SizeOfState_h + +#include "mozilla/fallible.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Unused.h" +#include "nsTHashtable.h" +#include "nsHashKeys.h" + +// This file includes types that are useful during memory reporting, but which +// cannot be put into mfbt/MemoryReporting.h because they depend on things that +// are not in MFBT. + +namespace mozilla { + +// A table of seen pointers. Useful when measuring structures that contain +// nodes that may be pointed to from multiple places, e.g. via RefPtr (in C++ +// code) or Arc (in Rust code). +class SeenPtrs : public nsTHashtable<nsPtrHashKey<const void>> { + public: + // Returns true if we have seen this pointer before, false otherwise. Also + // remembers this pointer for later queries. + bool HaveSeenPtr(const void* aPtr) { + uint32_t oldCount = Count(); + + mozilla::Unused << PutEntry(aPtr, fallible); + + // If the counts match, there are two possibilities. + // + // - Lookup succeeded: we've seen the pointer before, and didn't need to + // add a new entry. + // + // - PutEntry() tried to add the entry and failed due to lack of memory. In + // this case we can't tell if this pointer has been seen before (because + // the table is in an unreliable state and may have dropped previous + // insertions). When doing memory reporting it's better to err on the + // side of under-reporting rather than over-reporting, so we assume we've + // seen the pointer before. + // + return oldCount == Count(); + } +}; + +// Memory reporting state. Some memory measuring functions +// (SizeOfIncludingThis(), etc.) just need a MallocSizeOf parameter, but some +// also need a record of pointers that have been seen and should not be +// re-measured. This class encapsulates both of those things. +class SizeOfState { + public: + explicit SizeOfState(MallocSizeOf aMallocSizeOf) + : mMallocSizeOf(aMallocSizeOf) {} + + bool HaveSeenPtr(const void* aPtr) { return mSeenPtrs.HaveSeenPtr(aPtr); } + + MallocSizeOf mMallocSizeOf; + SeenPtrs mSeenPtrs; +}; + +} // namespace mozilla + +#endif // SizeOfState_h diff --git a/xpcom/base/StaticLocalPtr.h b/xpcom/base/StaticLocalPtr.h new file mode 100644 index 0000000000..2e2fc035bd --- /dev/null +++ b/xpcom/base/StaticLocalPtr.h @@ -0,0 +1,253 @@ +/* -*- 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 https://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_StaticLocalPtr_h +#define mozilla_StaticLocalPtr_h + +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/RefPtr.h" + +namespace mozilla { + +/** + * StaticLocalAutoPtr and StaticLocalRefPtr are like UniquePtr and RefPtr, + * except they are suitable for use as "magic static" local variables -- that + * is, they are able to take advantage of C++11's guarantee of thread safety + * during initialization by atomically constructing both the smart pointer + * itself as well as the object being pointed to. + * + * A static local instance of StaticLocal{Auto,Ref}Ptr does not cause the + * compiler to emit any atexit calls. In order to accomplish this, + * StaticLocal{Auto,Ref}Ptr must have a trivial destructor. As a consequence, + * it does not delete/release its raw pointer upon destruction. + * + * The clang plugin, run as part of our "static analysis" builds, makes it a + * compile-time error to use StaticLocal{Auto,Ref}Ptr as anything except a + * static local variable. + * + * StaticLocal{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 <typename T> +class MOZ_STATIC_LOCAL_CLASS StaticLocalAutoPtr final { + public: + explicit StaticLocalAutoPtr(T* aRawPtr) : mRawPtr(aRawPtr) {} + + StaticLocalAutoPtr(StaticLocalAutoPtr<T>&& aOther) : mRawPtr(aOther.mRawPtr) { + aOther.mRawPtr = nullptr; + } + + StaticLocalAutoPtr<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(); } + + T* forget() { + T* temp = mRawPtr; + mRawPtr = nullptr; + return temp; + } + + private: + StaticLocalAutoPtr(const StaticLocalAutoPtr<T>& aOther) = delete; + + // We do not allow assignment as the intention of this class is to only + // assign to mRawPtr during construction. + StaticLocalAutoPtr& operator=(const StaticLocalAutoPtr<T>& aOther) = delete; + StaticLocalAutoPtr& operator=(StaticLocalAutoPtr<T>&&) = delete; + + void Assign(T* aNewPtr) { + MOZ_ASSERT(!aNewPtr || mRawPtr != aNewPtr); + T* oldPtr = mRawPtr; + mRawPtr = aNewPtr; + delete oldPtr; + } + + T* mRawPtr; +}; + +template <typename T> +class MOZ_STATIC_LOCAL_CLASS StaticLocalRefPtr final { + public: + explicit StaticLocalRefPtr(T* aRawPtr) : mRawPtr(nullptr) { + AssignWithAddref(aRawPtr); + } + + explicit StaticLocalRefPtr(already_AddRefed<T>& aPtr) : mRawPtr(nullptr) { + AssignAssumingAddRef(aPtr.take()); + } + + explicit StaticLocalRefPtr(already_AddRefed<T>&& aPtr) : mRawPtr(nullptr) { + AssignAssumingAddRef(aPtr.take()); + } + + StaticLocalRefPtr(const StaticLocalRefPtr<T>& aPtr) + : StaticLocalRefPtr(aPtr.mRawPtr) {} + + StaticLocalRefPtr(StaticLocalRefPtr<T>&& aPtr) : mRawPtr(aPtr.mRawPtr) { + aPtr.mRawPtr = nullptr; + } + + StaticLocalRefPtr<T>& operator=(T* aRhs) { + AssignWithAddref(aRhs); + 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: + // We do not allow assignment as the intention of this class is to only + // assign to mRawPtr during construction. + StaticLocalRefPtr<T>& operator=(const StaticLocalRefPtr<T>& aRhs) = delete; + StaticLocalRefPtr<T>& operator=(StaticLocalRefPtr<T>&& aRhs) = delete; + + 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 StaticLocalPtr_internal { +class Zero; +} // namespace StaticLocalPtr_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); \ + } + +// StaticLocalAutoPtr (in)equality operators + +template <class T, class U> +inline bool operator==(const StaticLocalAutoPtr<T>& aLhs, + const StaticLocalAutoPtr<U>& aRhs) { + return aLhs.get() == aRhs.get(); +} + +template <class T, class U> +inline bool operator!=(const StaticLocalAutoPtr<T>& aLhs, + const StaticLocalAutoPtr<U>& aRhs) { + return !(aLhs == aRhs); +} + +REFLEXIVE_EQUALITY_OPERATORS(const StaticLocalAutoPtr<T>&, const U*, + lhs.get() == rhs, class T, class U) + +REFLEXIVE_EQUALITY_OPERATORS(const StaticLocalAutoPtr<T>&, U*, lhs.get() == rhs, + class T, class U) + +// Let us compare StaticLocalAutoPtr to 0. +REFLEXIVE_EQUALITY_OPERATORS(const StaticLocalAutoPtr<T>&, + StaticLocalPtr_internal::Zero*, + lhs.get() == nullptr, class T) + +// StaticLocalRefPtr (in)equality operators + +template <class T, class U> +inline bool operator==(const StaticLocalRefPtr<T>& aLhs, + const StaticLocalRefPtr<U>& aRhs) { + return aLhs.get() == aRhs.get(); +} + +template <class T, class U> +inline bool operator!=(const StaticLocalRefPtr<T>& aLhs, + const StaticLocalRefPtr<U>& aRhs) { + return !(aLhs == aRhs); +} + +REFLEXIVE_EQUALITY_OPERATORS(const StaticLocalRefPtr<T>&, const U*, + lhs.get() == rhs, class T, class U) + +REFLEXIVE_EQUALITY_OPERATORS(const StaticLocalRefPtr<T>&, U*, lhs.get() == rhs, + class T, class U) + +// Let us compare StaticLocalRefPtr to 0. +REFLEXIVE_EQUALITY_OPERATORS(const StaticLocalRefPtr<T>&, + StaticLocalPtr_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::StaticLocalRefPtr<U>& aOther) + : RefPtr(aOther.get()) {} + +template <class T> +template <class U> +RefPtr<T>& RefPtr<T>::operator=(const mozilla::StaticLocalRefPtr<U>& aOther) { + return operator=(aOther.get()); +} + +template <class T> +inline already_AddRefed<T> do_AddRef( + const mozilla::StaticLocalRefPtr<T>& aObj) { + RefPtr<T> ref(aObj); + return ref.forget(); +} + +#endif // mozilla_StaticLocalPtr_h diff --git a/xpcom/base/StaticMonitor.h b/xpcom/base/StaticMonitor.h new file mode 100644 index 0000000000..b35d3871d2 --- /dev/null +++ b/xpcom/base/StaticMonitor.h @@ -0,0 +1,117 @@ +/* -*- 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_StaticMonitor_h +#define mozilla_StaticMonitor_h + +#include "mozilla/Atomics.h" +#include "mozilla/CondVar.h" +#include "mozilla/ThreadSafety.h" + +namespace mozilla { + +class MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS MOZ_CAPABILITY("monitor") + StaticMonitor { + 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 + StaticMonitor() { MOZ_ASSERT(!mMutex); } +#endif + + void Lock() MOZ_CAPABILITY_ACQUIRE() { Mutex()->Lock(); } + + void Unlock() MOZ_CAPABILITY_RELEASE() { Mutex()->Unlock(); } + + void Wait() { CondVar()->Wait(); } + CVStatus Wait(TimeDuration aDuration) { + AssertCurrentThreadOwns(); + return CondVar()->Wait(aDuration); + } + + void Notify() { CondVar()->Notify(); } + void NotifyAll() { CondVar()->NotifyAll(); } + + void AssertCurrentThreadOwns() MOZ_ASSERT_CAPABILITY(this) { +#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; + } + + OffTheBooksCondVar* CondVar() { + if (mCondVar) { + return mCondVar; + } + + OffTheBooksCondVar* condvar = + new OffTheBooksCondVar(*Mutex(), "StaticCondVar"); + if (!mCondVar.compareExchange(nullptr, condvar)) { + delete condvar; + } + + return mCondVar; + } + + Atomic<OffTheBooksMutex*> mMutex; + Atomic<OffTheBooksCondVar*> mCondVar; + + // 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 + StaticMonitor(const StaticMonitor& aOther); +#endif + + // Disallow these operators. + StaticMonitor& operator=(const StaticMonitor& aRhs); + static void* operator new(size_t) noexcept(true); + static void operator delete(void*); +}; + +class MOZ_STACK_CLASS MOZ_SCOPED_CAPABILITY StaticMonitorAutoLock { + public: + explicit StaticMonitorAutoLock(StaticMonitor& aMonitor) + MOZ_CAPABILITY_ACQUIRE(aMonitor) + : mMonitor(&aMonitor) { + mMonitor->Lock(); + } + + ~StaticMonitorAutoLock() MOZ_CAPABILITY_RELEASE() { mMonitor->Unlock(); } + + void Wait() { mMonitor->Wait(); } + CVStatus Wait(TimeDuration aDuration) { return mMonitor->Wait(aDuration); } + + void Notify() { mMonitor->Notify(); } + void NotifyAll() { mMonitor->NotifyAll(); } + + private: + StaticMonitorAutoLock(); + StaticMonitorAutoLock(const StaticMonitorAutoLock&); + StaticMonitorAutoLock& operator=(const StaticMonitorAutoLock&); + static void* operator new(size_t) noexcept(true); + + StaticMonitor* mMonitor; +}; + +} // namespace mozilla + +#endif diff --git a/xpcom/base/StaticMutex.h b/xpcom/base/StaticMutex.h new file mode 100644 index 0000000000..08c0288486 --- /dev/null +++ b/xpcom/base/StaticMutex.h @@ -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/. */ + +#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 MOZ_CAPABILITY("mutex") + 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() MOZ_CAPABILITY_ACQUIRE() { Mutex()->Lock(); } + + void Unlock() MOZ_CAPABILITY_RELEASE() { Mutex()->Unlock(); } + + void AssertCurrentThreadOwns() MOZ_ASSERT_CAPABILITY(this) { +#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*, SequentiallyConsistent> 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) noexcept(true); + static void operator delete(void*); +}; + +typedef detail::BaseAutoLock<StaticMutex&> StaticMutexAutoLock; +typedef detail::BaseAutoUnlock<StaticMutex&> StaticMutexAutoUnlock; + +} // namespace mozilla + +#endif diff --git a/xpcom/base/StaticPtr.h b/xpcom/base/StaticPtr.h new file mode 100644 index 0000000000..38fcd82f97 --- /dev/null +++ b/xpcom/base/StaticPtr.h @@ -0,0 +1,235 @@ +/* -*- 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 UniquePtr and RefPtr, 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. + * + * Since the compiler guarantees that all global variables are initialized to + * 0, the default constexpr constructors will result in no actual code being + * generated. 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: + constexpr StaticAutoPtr() = default; + StaticAutoPtr(const StaticAutoPtr&) = delete; + + 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(); } + + T* forget() { + T* temp = mRawPtr; + mRawPtr = nullptr; + return temp; + } + + private: + void Assign(T* aNewPtr) { + MOZ_ASSERT(!aNewPtr || mRawPtr != aNewPtr); + T* oldPtr = mRawPtr; + mRawPtr = aNewPtr; + delete oldPtr; + } + + T* mRawPtr = nullptr; +}; + +template <class T> +class MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS StaticRefPtr { + public: + constexpr StaticRefPtr() = default; + StaticRefPtr(const StaticRefPtr&) = delete; + + 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; + } + + template <typename U> + StaticRefPtr<T>& operator=(RefPtr<U>&& aRhs) { + AssignAssumingAddRef(aRhs.forget().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) { + RefPtrTraits<T>::AddRef(aNewPtr); + } + AssignAssumingAddRef(aNewPtr); + } + + void AssignAssumingAddRef(T* aNewPtr) { + T* oldPtr = mRawPtr; + mRawPtr = aNewPtr; + if (oldPtr) { + RefPtrTraits<T>::Release(oldPtr); + } + } + + T* MOZ_OWNING_REF mRawPtr = nullptr; +}; + +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()); +} + +template <class T> +inline already_AddRefed<T> do_AddRef(const mozilla::StaticRefPtr<T>& aObj) { + RefPtr<T> ref(aObj); + return ref.forget(); +} + +#endif diff --git a/xpcom/base/components.conf b/xpcom/base/components.conf new file mode 100644 index 0000000000..4a22da67cf --- /dev/null +++ b/xpcom/base/components.conf @@ -0,0 +1,24 @@ +# -*- 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/. + +Classes = [ + { + 'cid': '{cb6cdb94-e417-4601-b4a5-f991bf41453d}', + 'contract_ids': ['@mozilla.org/xpcom/debug;1'], + 'legacy_constructor': 'nsDebugImpl::Create', + 'headers': ['nsDebugImpl.h'], + 'processes': ProcessSelector.ALLOW_IN_GPU_RDD_SOCKET_AND_UTILITY_PROCESS, + }, + { + 'cid': '{68bf4793-5204-45cf-9ee2-69adffbc2e38}', + 'contract_ids': ['@mozilla.org/xpcom/memory-watcher;1'], + 'singleton': True, + 'type': 'mozilla::nsAvailableMemoryWatcherBase', + 'headers': ['/xpcom/base/AvailableMemoryWatcher.h'], + 'constructor': 'mozilla::nsAvailableMemoryWatcherBase::GetSingleton', + 'processes': ProcessSelector.MAIN_PROCESS_ONLY, + }, +] diff --git a/xpcom/base/moz.build b/xpcom/base/moz.build new file mode 100644 index 0000000000..1ac409ed04 --- /dev/null +++ b/xpcom/base/moz.build @@ -0,0 +1,271 @@ +# -*- 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 += [ + "nsIAvailableMemoryWatcherBase.idl", + "nsIConsoleListener.idl", + "nsIConsoleMessage.idl", + "nsIConsoleService.idl", + "nsICycleCollectorListener.idl", + "nsIDebug2.idl", + "nsIException.idl", + "nsIInterfaceRequestor.idl", + "nsIMemoryInfoDumper.idl", + "nsIMemoryReporter.idl", + "nsISecurityConsoleMessage.idl", + "nsISupports.idl", + "nsIUUIDGenerator.idl", + "nsIVersionComparator.idl", + "nsIWeakReference.idl", + "nsrootidl.idl", +] + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa": + XPIDL_SOURCES += [ + "nsIMacPreferencesReader.idl", + ] + EXPORTS += [ + "nsObjCExceptions.h", + ] + EXPORTS.mozilla += [ + "MacHelpers.h", + "MacStringHelpers.h", + "nsMacPreferencesReader.h", + ] + UNIFIED_SOURCES += [ + "MacHelpers.mm", + "MacStringHelpers.mm", + "nsMacPreferencesReader.mm", + "nsObjCExceptions.mm", + ] + +XPIDL_MODULE = "xpcom_base" + +XPCOM_MANIFESTS += [ + "components.conf", +] + +EXPORTS += [ + "!ErrorList.h", + "!ErrorNamesInternal.h", + "CodeAddressService.h", + "nsAlgorithm.h", + "nsAutoRef.h", + "nsCom.h", + "nsCOMPtr.h", + "nscore.h", + "nsCRTGlue.h", + "nsCycleCollectionNoteChild.h", + "nsCycleCollectionNoteRootCallback.h", + "nsCycleCollectionParticipant.h", + "nsCycleCollectionTraversalCallback.h", + "nsCycleCollector.h", + "nsDebug.h", + "nsDebugImpl.h", + "nsDumpUtils.h", + "nsError.h", + "nsGZFileWriter.h", + "nsIClassInfoImpl.h", + "nsID.h", + "nsIDUtils.h", + "nsIInterfaceRequestorUtils.h", + "nsINIParser.h", + "nsInterfaceRequestorAgg.h", + "nsISizeOf.h", + "nsISupportsImpl.h", + "nsISupportsUtils.h", + "nsIWeakReferenceUtils.h", + "nsMaybeWeakPtr.h", + "nsMemory.h", + "nsMemoryReporterManager.h", + "nsQueryObject.h", + "nsSystemInfo.h", + "nsTraceRefcnt.h", + "nsVersionComparator.h", + "nsWeakReference.h", +] + +if CONFIG["OS_ARCH"] == "WINNT": + EXPORTS += [ + "nsWindowsHelpers.h", + ] + if CONFIG["CC_TYPE"] not in ("gcc", "clang"): + OS_LIBS += [ + "wscapi", + ] + +EXPORTS.mozilla += [ + "AppShutdown.h", + "AutoRestore.h", + "AvailableMemoryTracker.h", + "AvailableMemoryWatcher.h", + "ClearOnShutdown.h", + "CountingAllocatorBase.h", + "CycleCollectedJSContext.h", + "CycleCollectedJSRuntime.h", + "Debug.h", + "DebuggerOnGCRunnable.h", + "DeferredFinalize.h", + "EnumeratedArrayCycleCollection.h", + "ErrorNames.h", + "GkRustUtils.h", + "HoldDropJSObjects.h", + "IntentionalCrash.h", + "JSObjectHolder.h", + "JSONStringWriteFuncs.h", + "Logging.h", + "MemoryInfo.h", + "MemoryMapping.h", + "MemoryReportingProcess.h", + "MemoryTelemetry.h", + "nsMemoryInfoDumper.h", + "NSPRLogModulesParser.h", + "OwningNonNull.h", + "RLBoxSandboxPool.h", + "RLBoxUtils.h", + "ShutdownPhase.h", + "SizeOfState.h", + "StaticLocalPtr.h", + "StaticMonitor.h", + "StaticMutex.h", + "StaticPtr.h", +] + +SOURCES += [ + # nsDebugImpl isn't unified because we disable PGO so that NS_ABORT_OOM isn't + # optimized away oddly. + "nsDebugImpl.cpp", + # nsDumpUtils.cpp includes SpecialSystemDirectory.h which includes + # nsLocalFileMac.h which upsets other files in this dir that have a different + # idea about what `TextRange` means. + "nsDumpUtils.cpp", +] +SOURCES["nsDebugImpl.cpp"].no_pgo = True + +UNIFIED_SOURCES += [ + "AppShutdown.cpp", + "AvailableMemoryTracker.cpp", + "AvailableMemoryWatcher.cpp", + "ClearOnShutdown.cpp", + "CycleCollectedJSContext.cpp", + "CycleCollectedJSRuntime.cpp", + "Debug.cpp", + "DebuggerOnGCRunnable.cpp", + "DeferredFinalize.cpp", + "ErrorNames.cpp", + "GkRustUtils.cpp", + "HoldDropJSObjects.cpp", + "JSObjectHolder.cpp", + "LogCommandLineHandler.cpp", + "Logging.cpp", + "LogModulePrefWatcher.cpp", + "MemoryTelemetry.cpp", + "nsClassInfoImpl.cpp", + "nsCOMPtr.cpp", + "nsConsoleMessage.cpp", + "nsConsoleService.cpp", + "nsCRTGlue.cpp", + "nsCycleCollectionParticipant.cpp", + "nsCycleCollector.cpp", + "nsCycleCollectorTraceJSHelpers.cpp", + "nsGZFileWriter.cpp", + "nsID.cpp", + "nsIInterfaceRequestorUtils.cpp", + "nsINIParser.cpp", + "nsInterfaceRequestorAgg.cpp", + "nsISupportsImpl.cpp", + "nsMemoryImpl.cpp", + "nsMemoryInfoDumper.cpp", + "nsMemoryReporterManager.cpp", + "NSPRLogModulesParser.cpp", + "nsSecurityConsoleMessage.cpp", + "nsSystemInfo.cpp", + "nsTraceRefcnt.cpp", + "nsUUIDGenerator.cpp", + "nsVersionComparator.cpp", + "nsVersionComparatorImpl.cpp", + "nsWeakReference.cpp", + "RLBoxSandboxPool.cpp", +] + +if CONFIG["OS_TARGET"] in ("Linux", "Android"): + UNIFIED_SOURCES += [ + "MemoryMapping.cpp", + ] + +if CONFIG["OS_TARGET"] == "WINNT": + UNIFIED_SOURCES += [ + "AvailableMemoryWatcherWin.cpp", + "MemoryInfo.cpp", + ] + +if CONFIG["OS_TARGET"] == "Darwin": + UNIFIED_SOURCES += [ + "AvailableMemoryWatcherMac.cpp", + ] + EXPORTS.mozilla += [ + "MemoryPressureLevelMac.h", + ] + +if CONFIG["OS_TARGET"] == "Linux": + UNIFIED_SOURCES += [ + "AvailableMemoryWatcherLinux.cpp", + ] + EXPORTS.mozilla += [ + "AvailableMemoryWatcherUtils.h", + ] + +if CONFIG["MOZ_PHC"]: + DEFINES["MOZ_PHC"] = True + +GeneratedFile("ErrorList.h", script="ErrorList.py", entry_point="error_list_h") +GeneratedFile( + "ErrorNamesInternal.h", script="ErrorList.py", entry_point="error_names_internal_h" +) +GeneratedFile("error_list.rs", script="ErrorList.py", entry_point="error_list_rs") + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa": + SOURCES += [ + "nsMacUtilsImpl.cpp", + ] +elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows": + SOURCES += [ + "nsCrashOnException.cpp", + ] + +if CONFIG["COMPILE_ENVIRONMENT"]: + EXPORTS.mozilla += [ + "!gk_rust_utils_ffi_generated.h", + ] + + CbindgenHeader("gk_rust_utils_ffi_generated.h", inputs=["/xpcom/rust/gkrust_utils"]) + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul" + +LOCAL_INCLUDES += [ + "../build", + "/dom/base", + "/mfbt", + "/netwerk/base", + "/xpcom/ds", +] + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk": + CXXFLAGS += CONFIG["MOZ_GTK3_CFLAGS"] + +if CONFIG["MOZ_PHC"]: + EXPORTS.mozilla += [ + "PHCManager.h", + ] + + DEFINES["MOZ_PHC"] = 1 + + UNIFIED_SOURCES += ["PHCManager.cpp"] + +with Files("PHCManager.*"): + BUG_COMPONENT = ("Core", "Memory Allocator") diff --git a/xpcom/base/nsAlgorithm.h b/xpcom/base/nsAlgorithm.h new file mode 100644 index 0000000000..1c4bb0e9dc --- /dev/null +++ b/xpcom/base/nsAlgorithm.h @@ -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/. */ + +#ifndef nsAlgorithm_h___ +#define nsAlgorithm_h___ + +#include <cstdint> +#include "mozilla/Assertions.h" + +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; +} + +#endif // !defined(nsAlgorithm_h___) diff --git a/xpcom/base/nsAutoRef.h b/xpcom/base/nsAutoRef.h new file mode 100644 index 0000000000..9a1f3ddd93 --- /dev/null +++ b/xpcom/base/nsAutoRef.h @@ -0,0 +1,489 @@ +/* -*- 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/. */ + +// NB: This code may be used from non-XPCOM code, in particular, the +// Windows Default Browser Agent. + +#ifndef nsAutoRef_h_ +#define nsAutoRef_h_ + +#include "mozilla/Attributes.h" + +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 function return values see |nsReturnRef<T>|. + * + * For each class |T|, |nsAutoRefTraits<T>| or |nsSimpleRef<T>| must be + * specialized to use |nsAutoRef<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() = default; + + // 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. + + 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(); } + + explicit operator bool() const { return this->HaveResource(); } + + // 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(). + + // No copy constructor + explicit nsAutoRef(const ThisClass& aRefToSteal) = delete; +}; + +/** + * 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() = default; + + // 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) {} + + // Move construction transfers ownership + nsReturnRef(nsReturnRef<T>&& aRefToSteal) = default; + + 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>|. + * 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); + * }; + * + * 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>|. + * + * Specialize this class if there is no particular void value for the resource + * handle. A specialized implementation must also provide Release(RawRef), + */ + +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() = default; + + // 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 + 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() = default; + explicit LocalSimpleRef(RawRef aRawRef) : SimpleRef(aRawRef) {} + }; + + public: + ThisClass& operator=(const ThisClass& aSmartRef) = delete; + + 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/nsCOMPtr.cpp b/xpcom/base/nsCOMPtr.cpp new file mode 100644 index 0000000000..ffe0cb1acd --- /dev/null +++ b/xpcom/base/nsCOMPtr.cpp @@ -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/. */ + +#include "nsCOMPtr.h" + +nsresult nsQueryInterfaceISupports::operator()(const nsIID& aIID, + void** aAnswer) const { + nsresult status; + if (mRawPtr) { + status = mRawPtr->QueryInterface(aIID, aAnswer); + } else { + status = NS_ERROR_NULL_POINTER; + } + + return status; +} + +nsresult nsQueryInterfaceISupportsWithError::operator()(const nsIID& aIID, + void** aAnswer) const { + nsresult status; + if (mRawPtr) { + status = mRawPtr->QueryInterface(aIID, aAnswer); + } else { + status = NS_ERROR_NULL_POINTER; + } + + if (mErrorPtr) { + *mErrorPtr = status; + } + return status; +} diff --git a/xpcom/base/nsCOMPtr.h b/xpcom/base/nsCOMPtr.h new file mode 100644 index 0000000000..d45209d5aa --- /dev/null +++ b/xpcom/base/nsCOMPtr.h @@ -0,0 +1,1174 @@ +/* -*- 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 nsCOMPtr_h___ +#define nsCOMPtr_h___ + +/* + * Having problems? + * + * See the documentation at: + * https://firefox-source-docs.mozilla.org/xpcom/refptr.html + * + * + * nsCOMPtr + * better than a raw pointer + * for owning objects + * -- scc + */ + +#include <type_traits> + +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/RefPtr.h" +#include "nsCycleCollectionNoteChild.h" +#include "nsDebug.h" // for |NS_ASSERTION| +#include "nsISupportsUtils.h" // for |nsresult|, |NS_ADDREF|, |NS_GET_TEMPLATE_IID| et al + +/* + * WARNING: This file defines several macros for internal use only. These + * macros begin with the prefix |NSCAP_|. Do not use these macros in your own + * code. They are for internal use only for cross-platform compatibility, and + * are subject to change without notice. + */ + +#ifdef _MSC_VER +// Under VC++, we win by inlining StartAssignment. +# define NSCAP_FEATURE_INLINE_STARTASSIGNMENT + +// Also under VC++, at the highest warning level, we are overwhelmed with +// warnings about (unused) inline functions being removed. This is to be +// expected with templates, so we disable the warning. +# pragma warning(disable : 4514) +#endif + +#ifdef DEBUG +# define NSCAP_FEATURE_TEST_DONTQUERY_CASES +#endif + +#ifdef __GNUC__ +// Our use of nsCOMPtr_base::mRawPtr violates the C++ standard's aliasing +// rules. Mark it with the may_alias attribute so that gcc 3.3 and higher +// don't reorder instructions based on aliasing assumptions for +// this variable. Fortunately, gcc versions < 3.3 do not do any +// optimizations that break nsCOMPtr. + +# define NS_MAY_ALIAS_PTR(t) t* __attribute__((__may_alias__)) +#else +# define NS_MAY_ALIAS_PTR(t) t* +#endif + +/* + * The following three macros (NSCAP_ADDREF, NSCAP_RELEASE, and + * NSCAP_LOG_ASSIGNMENT) allow external clients the ability to add logging or + * other interesting debug facilities. In fact, if you want |nsCOMPtr| to + * participate in the standard logging facility, you provide + * (e.g., in "nsISupportsImpl.h") suitable definitions + * + * #define NSCAP_ADDREF(this, ptr) NS_ADDREF(ptr) + * #define NSCAP_RELEASE(this, ptr) NS_RELEASE(ptr) + */ + +#ifndef NSCAP_ADDREF +# define NSCAP_ADDREF(this, ptr) \ + mozilla::RefPtrTraits< \ + typename std::remove_reference<decltype(*ptr)>::type>::AddRef(ptr) +#endif + +#ifndef NSCAP_RELEASE +# define NSCAP_RELEASE(this, ptr) \ + mozilla::RefPtrTraits< \ + typename std::remove_reference<decltype(*ptr)>::type>::Release(ptr) +#endif + +// Clients can define |NSCAP_LOG_ASSIGNMENT| to perform logging. +#ifdef NSCAP_LOG_ASSIGNMENT +// Remember that |NSCAP_LOG_ASSIGNMENT| was defined by some client so that we +// know to instantiate |~nsGetterAddRefs| in turn to note the external +// assignment into the |nsCOMPtr|. +# define NSCAP_LOG_EXTERNAL_ASSIGNMENT +#else +// ...otherwise, just strip it out of the code +# define NSCAP_LOG_ASSIGNMENT(this, ptr) +#endif + +#ifndef NSCAP_LOG_RELEASE +# define NSCAP_LOG_RELEASE(this, ptr) +#endif + +namespace mozilla { +template <class T> +class OwningNonNull; +} // namespace mozilla + +template <class T> +inline already_AddRefed<T> dont_AddRef(T* aRawPtr) { + return already_AddRefed<T>(aRawPtr); +} + +template <class T> +inline already_AddRefed<T>&& dont_AddRef( + already_AddRefed<T>&& aAlreadyAddRefedPtr) { + return std::move(aAlreadyAddRefedPtr); +} + +/* + * An nsCOMPtr_helper transforms commonly called getters into typesafe forms + * that are more convenient to call, and more efficient to use with |nsCOMPtr|s. + * Good candidates for helpers are |QueryInterface()|, |CreateInstance()|, etc. + * + * Here are the rules for a helper: + * - it implements |operator()| to produce an interface pointer + * - (except for its name) |operator()| is a valid [XP]COM `getter' + * - the interface pointer that it returns is already |AddRef()|ed (as from + * any good getter) + * - it matches the type requested with the supplied |nsIID| argument + * - its constructor provides an optional |nsresult*| that |operator()| can + * fill in with an error when it is executed + * + * See |class nsGetInterface| for an example. + */ +class MOZ_STACK_CLASS nsCOMPtr_helper { + public: + virtual nsresult NS_FASTCALL operator()(const nsIID&, void**) const = 0; +}; + +/* + * nsQueryInterface could have been implemented as an nsCOMPtr_helper to avoid + * adding specialized machinery in nsCOMPtr, but do_QueryInterface is called + * often enough that the codesize savings are big enough to warrant the + * specialcasing. + */ +class MOZ_STACK_CLASS nsQueryInterfaceISupports { + public: + explicit nsQueryInterfaceISupports(nsISupports* aRawPtr) : mRawPtr(aRawPtr) {} + + nsresult NS_FASTCALL operator()(const nsIID& aIID, void**) const; + + private: + nsISupports* MOZ_OWNING_REF mRawPtr; +}; + +template <typename T> +class MOZ_STACK_CLASS nsQueryInterface final + : public nsQueryInterfaceISupports { + public: + explicit nsQueryInterface(T* aRawPtr) + : nsQueryInterfaceISupports(ToSupports(aRawPtr)) {} + + nsresult NS_FASTCALL operator()(const nsIID& aIID, void** aAnswer) const { + return nsQueryInterfaceISupports::operator()(aIID, aAnswer); + } +}; + +class MOZ_STACK_CLASS nsQueryInterfaceISupportsWithError { + public: + nsQueryInterfaceISupportsWithError(nsISupports* aRawPtr, nsresult* aError) + : mRawPtr(aRawPtr), mErrorPtr(aError) {} + + nsresult NS_FASTCALL operator()(const nsIID& aIID, void**) const; + + private: + nsISupports* MOZ_OWNING_REF mRawPtr; + nsresult* mErrorPtr; +}; + +template <typename T> +class MOZ_STACK_CLASS nsQueryInterfaceWithError final + : public nsQueryInterfaceISupportsWithError { + public: + explicit nsQueryInterfaceWithError(T* aRawPtr, nsresult* aError) + : nsQueryInterfaceISupportsWithError(ToSupports(aRawPtr), aError) {} + + nsresult NS_FASTCALL operator()(const nsIID& aIID, void** aAnswer) const { + return nsQueryInterfaceISupportsWithError::operator()(aIID, aAnswer); + } +}; + +namespace mozilla { +// PointedToType<> is needed so that do_QueryInterface() will work with a +// variety of smart pointer types in addition to raw pointers. These types +// include RefPtr<>, nsCOMPtr<>, and OwningNonNull<>. +template <class T> +using PointedToType = std::remove_pointer_t<decltype(&*std::declval<T>())>; +} // namespace mozilla + +template <class T> +inline nsQueryInterface<mozilla::PointedToType<T>> do_QueryInterface(T aPtr) { + return nsQueryInterface<mozilla::PointedToType<T>>(aPtr); +} + +template <class T> +inline nsQueryInterfaceWithError<mozilla::PointedToType<T>> do_QueryInterface( + T aRawPtr, nsresult* aError) { + return nsQueryInterfaceWithError<mozilla::PointedToType<T>>(aRawPtr, aError); +} + +template <class T> +inline void do_QueryInterface(already_AddRefed<T>&) { + // This signature exists solely to _stop_ you from doing the bad thing. + // Saying |do_QueryInterface()| on a pointer that is not otherwise owned by + // someone else is an automatic leak. See bug 8221. +} + +template <class T> +inline void do_QueryInterface(already_AddRefed<T>&, nsresult*) { + // This signature exists solely to _stop_ you from doing the bad thing. + // Saying |do_QueryInterface()| on a pointer that is not otherwise owned by + // someone else is an automatic leak. See bug 8221. +} + +//////////////////////////////////////////////////////////////////////////// +// Using servicemanager with COMPtrs +class nsGetServiceByCID final { + public: + explicit nsGetServiceByCID(const nsCID& aCID) : mCID(aCID) {} + + nsresult NS_FASTCALL operator()(const nsIID&, void**) const; + + private: + const nsCID& mCID; +}; + +class nsGetServiceByCIDWithError final { + public: + nsGetServiceByCIDWithError(const nsCID& aCID, nsresult* aErrorPtr) + : mCID(aCID), mErrorPtr(aErrorPtr) {} + + nsresult NS_FASTCALL operator()(const nsIID&, void**) const; + + private: + const nsCID& mCID; + nsresult* mErrorPtr; +}; + +class nsGetServiceByContractID final { + public: + explicit nsGetServiceByContractID(const char* aContractID) + : mContractID(aContractID) {} + + nsresult NS_FASTCALL operator()(const nsIID&, void**) const; + + private: + const char* mContractID; +}; + +class nsGetServiceByContractIDWithError final { + public: + nsGetServiceByContractIDWithError(const char* aContractID, + nsresult* aErrorPtr) + : mContractID(aContractID), mErrorPtr(aErrorPtr) {} + + nsresult NS_FASTCALL operator()(const nsIID&, void**) const; + + private: + const char* mContractID; + nsresult* mErrorPtr; +}; + +class nsIWeakReference; + +// Weak references +class MOZ_STACK_CLASS nsQueryReferent final { + public: + nsQueryReferent(nsIWeakReference* aWeakPtr, nsresult* aError) + : mWeakPtr(aWeakPtr), mErrorPtr(aError) {} + + nsresult NS_FASTCALL operator()(const nsIID& aIID, void**) const; + + private: + nsIWeakReference* MOZ_NON_OWNING_REF mWeakPtr; + nsresult* mErrorPtr; +}; + +// template<class T> class nsGetterAddRefs; + +// Helper for assert_validity method +template <class T> +char (&TestForIID(decltype(&NS_GET_TEMPLATE_IID(T))))[2]; +template <class T> +char TestForIID(...); + +template <class T> +class MOZ_IS_REFPTR nsCOMPtr final { + private: + void assign_with_AddRef(T*); + template <typename U> + void assign_from_qi(const nsQueryInterface<U>, const nsIID&); + template <typename U> + void assign_from_qi_with_error(const nsQueryInterfaceWithError<U>&, + const nsIID&); + void assign_from_gs_cid(const nsGetServiceByCID, const nsIID&); + void assign_from_gs_cid_with_error(const nsGetServiceByCIDWithError&, + const nsIID&); + void assign_from_gs_contractid(const nsGetServiceByContractID, const nsIID&); + void assign_from_gs_contractid_with_error( + const nsGetServiceByContractIDWithError&, const nsIID&); + void assign_from_query_referent(const nsQueryReferent&, const nsIID&); + void assign_from_helper(const nsCOMPtr_helper&, const nsIID&); + void** begin_assignment(); + + void assign_assuming_AddRef(T* aNewPtr) { + T* oldPtr = mRawPtr; + mRawPtr = aNewPtr; + NSCAP_LOG_ASSIGNMENT(this, aNewPtr); + NSCAP_LOG_RELEASE(this, oldPtr); + if (oldPtr) { + NSCAP_RELEASE(this, oldPtr); + } + } + + private: + T* MOZ_OWNING_REF mRawPtr; + + void assert_validity() { + static_assert(1 < sizeof(TestForIID<T>(nullptr)), + "nsCOMPtr only works " + "for types with IIDs. Either use RefPtr; add an IID to " + "your type with NS_DECLARE_STATIC_IID_ACCESSOR/" + "NS_DEFINE_STATIC_IID_ACCESSOR; or make the nsCOMPtr point " + "to a base class with an IID."); + } + + public: + typedef T element_type; + + ~nsCOMPtr() { + NSCAP_LOG_RELEASE(this, mRawPtr); + if (mRawPtr) { + NSCAP_RELEASE(this, mRawPtr); + } + } + +#ifdef NSCAP_FEATURE_TEST_DONTQUERY_CASES + void Assert_NoQueryNeeded() { + if (!mRawPtr) { + return; + } + if constexpr (std::is_same_v<T, nsISupports>) { + // FIXME: nsCOMPtr<nsISupports> never asserted this, and it currently + // fails... + return; + } + // This can't be defined in terms of do_QueryInterface because + // that bans casts from a class to itself. + void* out = nullptr; + mRawPtr->QueryInterface(NS_GET_TEMPLATE_IID(T), &out); + T* query_result = static_cast<T*>(out); + MOZ_ASSERT(query_result == mRawPtr, "QueryInterface needed"); + NS_RELEASE(query_result); + } + +# define NSCAP_ASSERT_NO_QUERY_NEEDED() Assert_NoQueryNeeded(); +#else +# define NSCAP_ASSERT_NO_QUERY_NEEDED() +#endif + + // Constructors + + nsCOMPtr() : mRawPtr(nullptr) { + assert_validity(); + NSCAP_LOG_ASSIGNMENT(this, nullptr); + } + + MOZ_IMPLICIT nsCOMPtr(decltype(nullptr)) : mRawPtr(nullptr) { + assert_validity(); + NSCAP_LOG_ASSIGNMENT(this, nullptr); + } + + nsCOMPtr(const nsCOMPtr<T>& aSmartPtr) : mRawPtr(aSmartPtr.mRawPtr) { + assert_validity(); + if (mRawPtr) { + NSCAP_ADDREF(this, mRawPtr); + } + NSCAP_LOG_ASSIGNMENT(this, aSmartPtr.mRawPtr); + } + + template <class U> + MOZ_IMPLICIT nsCOMPtr(const nsCOMPtr<U>& aSmartPtr) + : mRawPtr(aSmartPtr.get()) { + // Make sure that U actually inherits from T + static_assert(std::is_base_of<T, U>::value, "U should be a subclass of T"); + assert_validity(); + if (mRawPtr) { + NSCAP_ADDREF(this, mRawPtr); + } + NSCAP_LOG_ASSIGNMENT(this, aSmartPtr.get()); + } + + nsCOMPtr(nsCOMPtr<T>&& aSmartPtr) : mRawPtr(aSmartPtr.mRawPtr) { + assert_validity(); + aSmartPtr.mRawPtr = nullptr; + NSCAP_LOG_ASSIGNMENT(this, mRawPtr); + } + + template <class U> + MOZ_IMPLICIT nsCOMPtr(nsCOMPtr<U>&& aSmartPtr) + : mRawPtr(aSmartPtr.forget().template downcast<T>().take()) { + // Make sure that U actually inherits from T + static_assert(std::is_base_of<T, U>::value, "U should be a subclass of T"); + assert_validity(); + NSCAP_LOG_ASSIGNMENT(this, mRawPtr); + NSCAP_ASSERT_NO_QUERY_NEEDED(); + } + + MOZ_IMPLICIT nsCOMPtr(T* aRawPtr) : mRawPtr(aRawPtr) { + assert_validity(); + if (mRawPtr) { + NSCAP_ADDREF(this, mRawPtr); + } + NSCAP_LOG_ASSIGNMENT(this, aRawPtr); + NSCAP_ASSERT_NO_QUERY_NEEDED(); + } + + MOZ_IMPLICIT nsCOMPtr(already_AddRefed<T>& aSmartPtr) + : mRawPtr(aSmartPtr.take()) { + assert_validity(); + NSCAP_LOG_ASSIGNMENT(this, mRawPtr); + NSCAP_ASSERT_NO_QUERY_NEEDED(); + } + + // Construct from |otherComPtr.forget()|. + MOZ_IMPLICIT nsCOMPtr(already_AddRefed<T>&& aSmartPtr) + : mRawPtr(aSmartPtr.take()) { + assert_validity(); + NSCAP_LOG_ASSIGNMENT(this, mRawPtr); + NSCAP_ASSERT_NO_QUERY_NEEDED(); + } + + // Construct from |std::move(otherRefPtr)|. + template <typename U> + MOZ_IMPLICIT nsCOMPtr(RefPtr<U>&& aSmartPtr) + : mRawPtr(static_cast<already_AddRefed<T>>(aSmartPtr.forget()).take()) { + assert_validity(); + // Make sure that U actually inherits from T + static_assert(std::is_base_of<T, U>::value, "U is not a subclass of T"); + NSCAP_LOG_ASSIGNMENT(this, mRawPtr); + NSCAP_ASSERT_NO_QUERY_NEEDED(); + } + + // Construct from |already_AddRefed|. + template <typename U> + MOZ_IMPLICIT nsCOMPtr(already_AddRefed<U>& aSmartPtr) + : mRawPtr(static_cast<T*>(aSmartPtr.take())) { + assert_validity(); + // But make sure that U actually inherits from T. + static_assert(std::is_base_of<T, U>::value, "U is not a subclass of T"); + NSCAP_LOG_ASSIGNMENT(this, static_cast<T*>(mRawPtr)); + NSCAP_ASSERT_NO_QUERY_NEEDED(); + } + + // Construct from |otherComPtr.forget()|. + template <typename U> + MOZ_IMPLICIT nsCOMPtr(already_AddRefed<U>&& aSmartPtr) + : mRawPtr(static_cast<T*>(aSmartPtr.take())) { + assert_validity(); + // But make sure that U actually inherits from T. + static_assert(std::is_base_of<T, U>::value, "U is not a subclass of T"); + NSCAP_LOG_ASSIGNMENT(this, static_cast<T*>(mRawPtr)); + NSCAP_ASSERT_NO_QUERY_NEEDED(); + } + + // Construct from |do_QueryInterface(expr)|. + template <typename U> + MOZ_IMPLICIT nsCOMPtr(const nsQueryInterface<U> aQI) : mRawPtr(nullptr) { + assert_validity(); + NSCAP_LOG_ASSIGNMENT(this, nullptr); + assign_from_qi(aQI, NS_GET_TEMPLATE_IID(T)); + } + + // Construct from |do_QueryInterface(expr, &rv)|. + template <typename U> + MOZ_IMPLICIT nsCOMPtr(const nsQueryInterfaceWithError<U>& aQI) + : mRawPtr(nullptr) { + assert_validity(); + NSCAP_LOG_ASSIGNMENT(this, nullptr); + assign_from_qi_with_error(aQI, NS_GET_TEMPLATE_IID(T)); + } + + // Construct from |do_GetService(cid_expr)|. + MOZ_IMPLICIT nsCOMPtr(const nsGetServiceByCID aGS) : mRawPtr(nullptr) { + assert_validity(); + NSCAP_LOG_ASSIGNMENT(this, nullptr); + assign_from_gs_cid(aGS, NS_GET_TEMPLATE_IID(T)); + } + + // Construct from |do_GetService(cid_expr, &rv)|. + MOZ_IMPLICIT nsCOMPtr(const nsGetServiceByCIDWithError& aGS) + : mRawPtr(nullptr) { + assert_validity(); + NSCAP_LOG_ASSIGNMENT(this, nullptr); + assign_from_gs_cid_with_error(aGS, NS_GET_TEMPLATE_IID(T)); + } + + // Construct from |do_GetService(contractid_expr)|. + MOZ_IMPLICIT nsCOMPtr(const nsGetServiceByContractID aGS) : mRawPtr(nullptr) { + assert_validity(); + NSCAP_LOG_ASSIGNMENT(this, nullptr); + assign_from_gs_contractid(aGS, NS_GET_TEMPLATE_IID(T)); + } + + // Construct from |do_GetService(contractid_expr, &rv)|. + MOZ_IMPLICIT nsCOMPtr(const nsGetServiceByContractIDWithError& aGS) + : mRawPtr(nullptr) { + assert_validity(); + NSCAP_LOG_ASSIGNMENT(this, nullptr); + assign_from_gs_contractid_with_error(aGS, NS_GET_TEMPLATE_IID(T)); + } + + // Construct from |do_QueryReferent(ptr)| + MOZ_IMPLICIT nsCOMPtr(const nsQueryReferent& aQueryReferent) + : mRawPtr(nullptr) { + assert_validity(); + NSCAP_LOG_ASSIGNMENT(this, nullptr); + assign_from_query_referent(aQueryReferent, NS_GET_TEMPLATE_IID(T)); + } + + // And finally, anything else we might need to construct from can exploit the + // nsCOMPtr_helper facility. + MOZ_IMPLICIT nsCOMPtr(const nsCOMPtr_helper& aHelper) : mRawPtr(nullptr) { + assert_validity(); + NSCAP_LOG_ASSIGNMENT(this, nullptr); + assign_from_helper(aHelper, NS_GET_TEMPLATE_IID(T)); + NSCAP_ASSERT_NO_QUERY_NEEDED(); + } + + // construct from |mozilla::NotNull|. + template <typename I, + typename = std::enable_if_t<!std::is_same_v<I, nsCOMPtr<T>> && + std::is_convertible_v<I, nsCOMPtr<T>>>> + MOZ_IMPLICIT nsCOMPtr(const mozilla::NotNull<I>& aSmartPtr) + : mRawPtr(nsCOMPtr<T>(aSmartPtr.get()).forget().take()) {} + + // construct from |mozilla::MovingNotNull|. + template <typename I, + typename = std::enable_if_t<!std::is_same_v<I, nsCOMPtr<T>> && + std::is_convertible_v<I, nsCOMPtr<T>>>> + MOZ_IMPLICIT nsCOMPtr(mozilla::MovingNotNull<I>&& aSmartPtr) + : mRawPtr( + nsCOMPtr<T>(std::move(aSmartPtr).unwrapBasePtr()).forget().take()) { + } + + // Defined in OwningNonNull.h + template <class U> + MOZ_IMPLICIT nsCOMPtr(const mozilla::OwningNonNull<U>& aOther); + + // Assignment operators + + nsCOMPtr<T>& operator=(const nsCOMPtr<T>& aRhs) { + assign_with_AddRef(aRhs.mRawPtr); + return *this; + } + + template <class U> + nsCOMPtr<T>& operator=(const nsCOMPtr<U>& aRhs) { + // Make sure that U actually inherits from T + static_assert(std::is_base_of<T, U>::value, "U should be a subclass of T"); + assign_with_AddRef(aRhs.get()); + return *this; + } + + nsCOMPtr<T>& operator=(nsCOMPtr<T>&& aRhs) { + assign_assuming_AddRef(aRhs.forget().take()); + return *this; + } + + template <class U> + nsCOMPtr<T>& operator=(nsCOMPtr<U>&& aRhs) { + // Make sure that U actually inherits from T + static_assert(std::is_base_of<T, U>::value, "U should be a subclass of T"); + assign_assuming_AddRef(aRhs.forget().template downcast<T>().take()); + NSCAP_ASSERT_NO_QUERY_NEEDED(); + return *this; + } + + nsCOMPtr<T>& operator=(T* aRhs) { + assign_with_AddRef(aRhs); + NSCAP_ASSERT_NO_QUERY_NEEDED(); + return *this; + } + + nsCOMPtr<T>& operator=(decltype(nullptr)) { + assign_assuming_AddRef(nullptr); + return *this; + } + + // Assign from |already_AddRefed|. + template <typename U> + nsCOMPtr<T>& operator=(already_AddRefed<U>& aRhs) { + // Make sure that U actually inherits from T + static_assert(std::is_base_of<T, U>::value, "U is not a subclass of T"); + assign_assuming_AddRef(static_cast<T*>(aRhs.take())); + NSCAP_ASSERT_NO_QUERY_NEEDED(); + return *this; + } + + // Assign from |otherComPtr.forget()|. + template <typename U> + nsCOMPtr<T>& operator=(already_AddRefed<U>&& aRhs) { + // Make sure that U actually inherits from T + static_assert(std::is_base_of<T, U>::value, "U is not a subclass of T"); + assign_assuming_AddRef(static_cast<T*>(aRhs.take())); + NSCAP_ASSERT_NO_QUERY_NEEDED(); + return *this; + } + + // Assign from |std::move(otherRefPtr)|. + template <typename U> + nsCOMPtr<T>& operator=(RefPtr<U>&& aRhs) { + // Make sure that U actually inherits from T + static_assert(std::is_base_of<T, U>::value, "U is not a subclass of T"); + assign_assuming_AddRef(static_cast<T*>(aRhs.forget().take())); + NSCAP_ASSERT_NO_QUERY_NEEDED(); + return *this; + } + + // Assign from |do_QueryInterface(expr)|. + template <typename U> + nsCOMPtr<T>& operator=(const nsQueryInterface<U> aRhs) { + assign_from_qi(aRhs, NS_GET_TEMPLATE_IID(T)); + return *this; + } + + // Assign from |do_QueryInterface(expr, &rv)|. + template <typename U> + nsCOMPtr<T>& operator=(const nsQueryInterfaceWithError<U>& aRhs) { + assign_from_qi_with_error(aRhs, NS_GET_TEMPLATE_IID(T)); + return *this; + } + + // Assign from |do_GetService(cid_expr)|. + nsCOMPtr<T>& operator=(const nsGetServiceByCID aRhs) { + assign_from_gs_cid(aRhs, NS_GET_TEMPLATE_IID(T)); + return *this; + } + + // Assign from |do_GetService(cid_expr, &rv)|. + nsCOMPtr<T>& operator=(const nsGetServiceByCIDWithError& aRhs) { + assign_from_gs_cid_with_error(aRhs, NS_GET_TEMPLATE_IID(T)); + return *this; + } + + // Assign from |do_GetService(contractid_expr)|. + nsCOMPtr<T>& operator=(const nsGetServiceByContractID aRhs) { + assign_from_gs_contractid(aRhs, NS_GET_TEMPLATE_IID(T)); + return *this; + } + + // Assign from |do_GetService(contractid_expr, &rv)|. + nsCOMPtr<T>& operator=(const nsGetServiceByContractIDWithError& aRhs) { + assign_from_gs_contractid_with_error(aRhs, NS_GET_TEMPLATE_IID(T)); + return *this; + } + + // Assign from |do_QueryReferent(ptr)|. + nsCOMPtr<T>& operator=(const nsQueryReferent& aRhs) { + assign_from_query_referent(aRhs, NS_GET_TEMPLATE_IID(T)); + return *this; + } + + // And finally, anything else we might need to assign from can exploit the + // nsCOMPtr_helper facility. + nsCOMPtr<T>& operator=(const nsCOMPtr_helper& aRhs) { + assign_from_helper(aRhs, NS_GET_TEMPLATE_IID(T)); + NSCAP_ASSERT_NO_QUERY_NEEDED(); + return *this; + } + + // Assign from |mozilla::NotNull|. + template <typename I, + typename = std::enable_if_t<std::is_convertible_v<I, nsCOMPtr<T>>>> + nsCOMPtr<T>& operator=(const mozilla::NotNull<I>& aSmartPtr) { + assign_assuming_AddRef(nsCOMPtr<T>(aSmartPtr.get()).forget().take()); + return *this; + } + + // Assign from |mozilla::MovingNotNull|. + template <typename I, + typename = std::enable_if_t<std::is_convertible_v<I, nsCOMPtr<T>>>> + nsCOMPtr<T>& operator=(mozilla::MovingNotNull<I>&& aSmartPtr) { + assign_assuming_AddRef( + nsCOMPtr<T>(std::move(aSmartPtr).unwrapBasePtr()).forget().take()); + return *this; + } + + // Defined in OwningNonNull.h + template <class U> + nsCOMPtr<T>& operator=(const mozilla::OwningNonNull<U>& aOther); + + // Exchange ownership with |aRhs|; can save a pair of refcount operations. + void swap(nsCOMPtr<T>& aRhs) { + T* temp = aRhs.mRawPtr; + NSCAP_LOG_ASSIGNMENT(&aRhs, mRawPtr); + NSCAP_LOG_ASSIGNMENT(this, temp); + NSCAP_LOG_RELEASE(this, mRawPtr); + NSCAP_LOG_RELEASE(&aRhs, temp); + aRhs.mRawPtr = mRawPtr; + mRawPtr = temp; + // |aRhs| maintains the same invariants, so we don't need to + // |NSCAP_ASSERT_NO_QUERY_NEEDED| + } + + // Exchange ownership with |aRhs|; can save a pair of refcount operations. + void swap(T*& aRhs) { + T* temp = aRhs; + NSCAP_LOG_ASSIGNMENT(this, temp); + NSCAP_LOG_RELEASE(this, mRawPtr); + aRhs = reinterpret_cast<T*>(mRawPtr); + mRawPtr = temp; + NSCAP_ASSERT_NO_QUERY_NEEDED(); + } + + // Other pointer operators + + // Return the value of mRawPtr and null out mRawPtr. Useful for + // already_AddRefed return values. + already_AddRefed<T> MOZ_MAY_CALL_AFTER_MUST_RETURN forget() { + T* temp = nullptr; + swap(temp); + return already_AddRefed<T>(temp); + } + + // Set the target of aRhs to the value of mRawPtr and null out mRawPtr. + // Useful to avoid unnecessary AddRef/Release pairs with "out" parameters + // where aRhs bay be a T** or an I** where I is a base class of T. + template <typename I> + void forget(I** aRhs) { + NS_ASSERTION(aRhs, "Null pointer passed to forget!"); + NSCAP_LOG_RELEASE(this, mRawPtr); + *aRhs = get(); + mRawPtr = nullptr; + } + + // Prefer the implicit conversion provided automatically by + // |operator T*() const|. Use |get()| to resolve ambiguity or to get a + // castable pointer. + T* get() const { return reinterpret_cast<T*>(mRawPtr); } + + // Makes an nsCOMPtr 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 nsCOMPtr substitutable for a raw pointer. + // + // Prefer the implicit use of this operator to calling |get()|, except where + // necessary to resolve ambiguity. + operator T*() const& { return get(); } + + // Don't allow implicit conversion of temporary nsCOMPtr to raw pointer, + // because the refcount might be one and the pointer will immediately become + // invalid. + operator T*() const&& = delete; + + // Needed to avoid the deleted operator above + explicit operator bool() const { return !!mRawPtr; } + + T* operator->() const MOZ_NO_ADDREF_RELEASE_ON_RETURN { + MOZ_ASSERT(mRawPtr != nullptr, + "You can't dereference a NULL nsCOMPtr with operator->()."); + return get(); + } + + // These are not intended to be used by clients. See |address_of| below. + nsCOMPtr<T>* get_address() { return this; } + const nsCOMPtr<T>* get_address() const { return this; } + + public: + T& operator*() const { + MOZ_ASSERT(mRawPtr != nullptr, + "You can't dereference a NULL nsCOMPtr with operator*()."); + return *get(); + } + + T** StartAssignment() { +#ifndef NSCAP_FEATURE_INLINE_STARTASSIGNMENT + return reinterpret_cast<T**>(begin_assignment()); +#else + assign_assuming_AddRef(nullptr); + return reinterpret_cast<T**>(&mRawPtr); +#endif + } +}; + +template <typename T> +inline void ImplCycleCollectionUnlink(nsCOMPtr<T>& aField) { + aField = nullptr; +} + +template <typename T> +inline void ImplCycleCollectionTraverse( + nsCycleCollectionTraversalCallback& aCallback, nsCOMPtr<T>& aField, + const char* aName, uint32_t aFlags = 0) { + CycleCollectionNoteChild(aCallback, aField.get(), aName, aFlags); +} + +template <class T> +void nsCOMPtr<T>::assign_with_AddRef(T* aRawPtr) { + if (aRawPtr) { + NSCAP_ADDREF(this, aRawPtr); + } + assign_assuming_AddRef(aRawPtr); +} + +template <class T> +template <typename U> +void nsCOMPtr<T>::assign_from_qi(const nsQueryInterface<U> aQI, + const nsIID& aIID) { + // Allow QIing to nsISupports from nsISupports as a special-case, since + // SameCOMIdentity uses it. + static_assert( + std::is_same_v<T, nsISupports> || + !(std::is_same_v<T, U> || std::is_base_of<T, U>::value), + "don't use do_QueryInterface for compile-time-determinable casts"); + void* newRawPtr; + if (NS_FAILED(aQI(aIID, &newRawPtr))) { + newRawPtr = nullptr; + } + assign_assuming_AddRef(static_cast<T*>(newRawPtr)); +} + +template <class T> +template <typename U> +void nsCOMPtr<T>::assign_from_qi_with_error( + const nsQueryInterfaceWithError<U>& aQI, const nsIID& aIID) { + static_assert( + !(std::is_same_v<T, U> || std::is_base_of<T, U>::value), + "don't use do_QueryInterface for compile-time-determinable casts"); + void* newRawPtr; + if (NS_FAILED(aQI(aIID, &newRawPtr))) { + newRawPtr = nullptr; + } + assign_assuming_AddRef(static_cast<T*>(newRawPtr)); +} + +template <class T> +void nsCOMPtr<T>::assign_from_gs_cid(const nsGetServiceByCID aGS, + const nsIID& aIID) { + void* newRawPtr; + if (NS_FAILED(aGS(aIID, &newRawPtr))) { + newRawPtr = nullptr; + } + assign_assuming_AddRef(static_cast<T*>(newRawPtr)); +} + +template <class T> +void nsCOMPtr<T>::assign_from_gs_cid_with_error( + const nsGetServiceByCIDWithError& aGS, const nsIID& aIID) { + void* newRawPtr; + if (NS_FAILED(aGS(aIID, &newRawPtr))) { + newRawPtr = nullptr; + } + assign_assuming_AddRef(static_cast<T*>(newRawPtr)); +} + +template <class T> +void nsCOMPtr<T>::assign_from_gs_contractid(const nsGetServiceByContractID aGS, + const nsIID& aIID) { + void* newRawPtr; + if (NS_FAILED(aGS(aIID, &newRawPtr))) { + newRawPtr = nullptr; + } + assign_assuming_AddRef(static_cast<T*>(newRawPtr)); +} + +template <class T> +void nsCOMPtr<T>::assign_from_gs_contractid_with_error( + const nsGetServiceByContractIDWithError& aGS, const nsIID& aIID) { + void* newRawPtr; + if (NS_FAILED(aGS(aIID, &newRawPtr))) { + newRawPtr = nullptr; + } + assign_assuming_AddRef(static_cast<T*>(newRawPtr)); +} + +template <class T> +void nsCOMPtr<T>::assign_from_query_referent( + const nsQueryReferent& aQueryReferent, const nsIID& aIID) { + void* newRawPtr; + if (NS_FAILED(aQueryReferent(aIID, &newRawPtr))) { + newRawPtr = nullptr; + } + assign_assuming_AddRef(static_cast<T*>(newRawPtr)); +} + +template <class T> +void nsCOMPtr<T>::assign_from_helper(const nsCOMPtr_helper& helper, + const nsIID& aIID) { + void* newRawPtr; + if (NS_FAILED(helper(aIID, &newRawPtr))) { + newRawPtr = nullptr; + } + assign_assuming_AddRef(static_cast<T*>(newRawPtr)); +} + +template <class T> +void** nsCOMPtr<T>::begin_assignment() { + assign_assuming_AddRef(nullptr); + union { + T** mT; + void** mVoid; + } result; + result.mT = &mRawPtr; + return result.mVoid; +} + +template <class T> +inline nsCOMPtr<T>* address_of(nsCOMPtr<T>& aPtr) { + return aPtr.get_address(); +} + +template <class T> +inline const nsCOMPtr<T>* address_of(const nsCOMPtr<T>& aPtr) { + return aPtr.get_address(); +} + +/** + * This class is designed to be used for anonymous temporary objects in the + * argument list of calls that return COM interface pointers, e.g., + * + * nsCOMPtr<IFoo> fooP; + * ...->QueryInterface(iid, getter_AddRefs(fooP)) + * + * DO NOT USE THIS TYPE DIRECTLY IN YOUR CODE. Use |getter_AddRefs()| instead. + * + * When initialized with a |nsCOMPtr|, as in the example above, it returns + * a |void**|, a |T**|, or an |nsISupports**| as needed, that the outer call + * (|QueryInterface| in this case) can fill in. + * + * This type should be a nested class inside |nsCOMPtr<T>|. + */ +template <class T> +class nsGetterAddRefs { + public: + explicit nsGetterAddRefs(nsCOMPtr<T>& aSmartPtr) + : mTargetSmartPtr(aSmartPtr) {} + +#if defined(NSCAP_FEATURE_TEST_DONTQUERY_CASES) || \ + defined(NSCAP_LOG_EXTERNAL_ASSIGNMENT) + ~nsGetterAddRefs() { +# ifdef NSCAP_LOG_EXTERNAL_ASSIGNMENT + NSCAP_LOG_ASSIGNMENT(reinterpret_cast<void*>(address_of(mTargetSmartPtr)), + mTargetSmartPtr.get()); +# endif + +# ifdef NSCAP_FEATURE_TEST_DONTQUERY_CASES + mTargetSmartPtr.Assert_NoQueryNeeded(); +# endif + } +#endif + + operator void**() { + return reinterpret_cast<void**>(mTargetSmartPtr.StartAssignment()); + } + + operator T**() { return mTargetSmartPtr.StartAssignment(); } + T*& operator*() { return *(mTargetSmartPtr.StartAssignment()); } + + private: + nsCOMPtr<T>& mTargetSmartPtr; +}; + +template <> +class nsGetterAddRefs<nsISupports> { + public: + explicit nsGetterAddRefs(nsCOMPtr<nsISupports>& aSmartPtr) + : mTargetSmartPtr(aSmartPtr) {} + +#ifdef NSCAP_LOG_EXTERNAL_ASSIGNMENT + ~nsGetterAddRefs() { + NSCAP_LOG_ASSIGNMENT(reinterpret_cast<void*>(address_of(mTargetSmartPtr)), + mTargetSmartPtr.get()); + } +#endif + + operator void**() { + return reinterpret_cast<void**>(mTargetSmartPtr.StartAssignment()); + } + + operator nsISupports**() { return mTargetSmartPtr.StartAssignment(); } + nsISupports*& operator*() { return *(mTargetSmartPtr.StartAssignment()); } + + private: + nsCOMPtr<nsISupports>& mTargetSmartPtr; +}; + +template <class T> +inline nsGetterAddRefs<T> getter_AddRefs(nsCOMPtr<T>& aSmartPtr) { + return nsGetterAddRefs<T>(aSmartPtr); +} + +template <class T, class DestinationType> +inline nsresult CallQueryInterface( + T* aSource, nsGetterAddRefs<DestinationType> aDestination) { + return CallQueryInterface(aSource, + static_cast<DestinationType**>(aDestination)); +} + +// Comparing two |nsCOMPtr|s + +template <class T, class U> +inline bool operator==(const nsCOMPtr<T>& aLhs, const nsCOMPtr<U>& aRhs) { + return static_cast<const T*>(aLhs.get()) == static_cast<const U*>(aRhs.get()); +} + +template <class T, class U> +inline bool operator!=(const nsCOMPtr<T>& aLhs, const nsCOMPtr<U>& aRhs) { + return static_cast<const T*>(aLhs.get()) != static_cast<const U*>(aRhs.get()); +} + +// Comparing an |nsCOMPtr| to a raw pointer + +template <class T, class U> +inline bool operator==(const nsCOMPtr<T>& aLhs, const U* aRhs) { + return static_cast<const T*>(aLhs.get()) == aRhs; +} + +template <class T, class U> +inline bool operator==(const U* aLhs, const nsCOMPtr<T>& aRhs) { + return aLhs == static_cast<const T*>(aRhs.get()); +} + +template <class T, class U> +inline bool operator!=(const nsCOMPtr<T>& aLhs, const U* aRhs) { + return static_cast<const T*>(aLhs.get()) != aRhs; +} + +template <class T, class U> +inline bool operator!=(const U* aLhs, const nsCOMPtr<T>& aRhs) { + return aLhs != static_cast<const T*>(aRhs.get()); +} + +template <class T, class U> +inline bool operator==(const nsCOMPtr<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 nsCOMPtr<T>& aRhs) { + return const_cast<const U*>(aLhs) == static_cast<const T*>(aRhs.get()); +} + +template <class T, class U> +inline bool operator!=(const nsCOMPtr<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 nsCOMPtr<T>& aRhs) { + return const_cast<const U*>(aLhs) != static_cast<const T*>(aRhs.get()); +} + +// Comparing an |nsCOMPtr| to |nullptr| + +template <class T> +inline bool operator==(const nsCOMPtr<T>& aLhs, decltype(nullptr)) { + return aLhs.get() == nullptr; +} + +template <class T> +inline bool operator==(decltype(nullptr), const nsCOMPtr<T>& aRhs) { + return nullptr == aRhs.get(); +} + +template <class T> +inline bool operator!=(const nsCOMPtr<T>& aLhs, decltype(nullptr)) { + return aLhs.get() != nullptr; +} + +template <class T> +inline bool operator!=(decltype(nullptr), const nsCOMPtr<T>& aRhs) { + return nullptr != aRhs.get(); +} + +// Comparing any two [XP]COM objects for identity + +inline bool SameCOMIdentity(nsISupports* aLhs, nsISupports* aRhs) { + return nsCOMPtr<nsISupports>(do_QueryInterface(aLhs)) == + nsCOMPtr<nsISupports>(do_QueryInterface(aRhs)); +} + +template <class SourceType, class DestinationType> +inline nsresult CallQueryInterface(nsCOMPtr<SourceType>& aSourcePtr, + DestinationType** aDestPtr) { + return CallQueryInterface(aSourcePtr.get(), aDestPtr); +} + +template <class T> +RefPtr<T>::RefPtr(const nsQueryReferent& aQueryReferent) { + void* newRawPtr; + if (NS_FAILED(aQueryReferent(NS_GET_TEMPLATE_IID(T), &newRawPtr))) { + newRawPtr = nullptr; + } + mRawPtr = static_cast<T*>(newRawPtr); +} + +template <class T> +RefPtr<T>::RefPtr(const nsCOMPtr_helper& aHelper) { + void* newRawPtr; + if (NS_FAILED(aHelper(NS_GET_TEMPLATE_IID(T), &newRawPtr))) { + newRawPtr = nullptr; + } + mRawPtr = static_cast<T*>(newRawPtr); +} + +template <class T> +RefPtr<T>& RefPtr<T>::operator=(const nsQueryReferent& aQueryReferent) { + void* newRawPtr; + if (NS_FAILED(aQueryReferent(NS_GET_TEMPLATE_IID(T), &newRawPtr))) { + newRawPtr = nullptr; + } + assign_assuming_AddRef(static_cast<T*>(newRawPtr)); + return *this; +} + +template <class T> +RefPtr<T>& RefPtr<T>::operator=(const nsCOMPtr_helper& aHelper) { + void* newRawPtr; + if (NS_FAILED(aHelper(NS_GET_TEMPLATE_IID(T), &newRawPtr))) { + newRawPtr = nullptr; + } + assign_assuming_AddRef(static_cast<T*>(newRawPtr)); + return *this; +} + +template <class T> +inline already_AddRefed<T> do_AddRef(const nsCOMPtr<T>& aObj) { + nsCOMPtr<T> ref(aObj); + return ref.forget(); +} + +// MOZ_DBG support + +template <class T> +std::ostream& operator<<(std::ostream& aOut, const nsCOMPtr<T>& aObj) { + return mozilla::DebugValue(aOut, aObj.get()); +} + +// ToRefPtr allows to move an nsCOMPtr<T> into a RefPtr<T>. Be mindful when +// using this, because usually RefPtr<T> should only be used with concrete T and +// nsCOMPtr<T> should only be used with XPCOM interface T. +template <class T> +RefPtr<T> ToRefPtr(nsCOMPtr<T>&& aObj) { + return aObj.forget(); +} + +// Integration with ResultExtensions.h +template <typename R> +auto ResultRefAsParam(nsCOMPtr<R>& aResult) { + return getter_AddRefs(aResult); +} + +namespace mozilla::detail { +template <typename T> +struct outparam_as_pointer; + +template <typename T> +struct outparam_as_pointer<nsGetterAddRefs<T>> { + using type = T**; +}; +} // namespace mozilla::detail + +#endif // !defined(nsCOMPtr_h___) diff --git a/xpcom/base/nsCRTGlue.cpp b/xpcom/base/nsCRTGlue.cpp new file mode 100644 index 0000000000..99289daa63 --- /dev/null +++ b/xpcom/base/nsCRTGlue.cpp @@ -0,0 +1,226 @@ +/* -*- 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 "nsCRTGlue.h" +#include "nsXPCOM.h" +#include "nsDebug.h" +#include "prtime.h" + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <stdarg.h> + +#include "mozilla/Sprintf.h" + +#ifdef XP_WIN +# include <io.h> +# include <windows.h> +#endif + +#ifdef FUZZING_SNAPSHOT +# include <mozilla/fuzzing/NyxWrapper.h> +#endif + +using namespace mozilla; + +const char* NS_strspnp(const char* aDelims, const char* aStr) { + const char* d; + do { + for (d = aDelims; *d != '\0'; ++d) { + if (*aStr == *d) { + ++aStr; + break; + } + } + } while (*d); + + return aStr; +} + +char* NS_strtok(const char* aDelims, char** aStr) { + if (!*aStr) { + return nullptr; + } + + char* ret = (char*)NS_strspnp(aDelims, *aStr); + + if (!*ret) { + *aStr = ret; + return nullptr; + } + + char* i = ret; + do { + for (const char* d = aDelims; *d != '\0'; ++d) { + if (*i == *d) { + *i = '\0'; + *aStr = ++i; + return ret; + } + } + ++i; + } while (*i); + + *aStr = nullptr; + return ret; +} + +uint32_t NS_strlen(const char16_t* aString) { + MOZ_ASSERT(aString); + const char16_t* end; + + for (end = aString; *end; ++end) { + // empty loop + } + + return end - aString; +} + +int NS_strcmp(const char16_t* aStrA, const char16_t* aStrB) { + while (*aStrB) { + int r = *aStrA - *aStrB; + if (r) { + return r; + } + + ++aStrA; + ++aStrB; + } + + return *aStrA != '\0'; +} + +int NS_strncmp(const char16_t* aStrA, const char16_t* aStrB, size_t aLen) { + while (aLen && *aStrB) { + int r = *aStrA - *aStrB; + if (r) { + return r; + } + + ++aStrA; + ++aStrB; + --aLen; + } + + return aLen ? *aStrA != '\0' : 0; +} + +char16_t* NS_xstrdup(const char16_t* aString) { + uint32_t len = NS_strlen(aString); + return NS_xstrndup(aString, len); +} + +template <typename CharT> +CharT* NS_xstrndup(const CharT* aString, uint32_t aLen) { + auto newBuf = (CharT*)moz_xmalloc((aLen + 1) * sizeof(CharT)); + memcpy(newBuf, aString, aLen * sizeof(CharT)); + newBuf[aLen] = '\0'; + return newBuf; +} + +template char16_t* NS_xstrndup<char16_t>(const char16_t* aString, + uint32_t aLen); +template char* NS_xstrndup<char>(const char* aString, uint32_t aLen); + +char* NS_xstrdup(const char* aString) { + uint32_t len = strlen(aString); + char* str = (char*)moz_xmalloc(len + 1); + memcpy(str, aString, len); + str[len] = '\0'; + return str; +} + +// clang-format off + +// This table maps uppercase characters to lower case characters; +// characters that are neither upper nor lower case are unaffected. +const unsigned char nsLowerUpperUtils::kUpper2Lower[256] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 64, + + // upper band mapped to lower [A-Z] => [a-z] + 97, 98, 99,100,101,102,103,104,105,106,107,108,109,110,111, + 112,113,114,115,116,117,118,119,120,121,122, + + 91, 92, 93, 94, 95, + 96, 97, 98, 99,100,101,102,103,104,105,106,107,108,109,110,111, + 112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127, + 128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143, + 144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159, + 160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175, + 176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191, + 192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207, + 208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223, + 224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239, + 240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255 +}; + +const unsigned char nsLowerUpperUtils::kLower2Upper[256] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, + 96, + + // lower band mapped to upper [a-z] => [A-Z] + 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, + + 123,124,125,126,127, + 128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143, + 144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159, + 160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175, + 176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191, + 192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207, + 208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223, + 224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239, + 240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255 +}; + +// clang-format on + +bool NS_IsUpper(char aChar) { + return aChar != (char)nsLowerUpperUtils::kUpper2Lower[(unsigned char)aChar]; +} + +bool NS_IsLower(char aChar) { + return aChar != (char)nsLowerUpperUtils::kLower2Upper[(unsigned char)aChar]; +} + +#ifndef XPCOM_GLUE_AVOID_NSPR + +void NS_MakeRandomString(char* aBuf, int32_t aBufLen) { +# define TABLE_SIZE 36 + static const char table[] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', + 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', + 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', + '1', '2', '3', '4', '5', '6', '7', '8', '9'}; + + // turn PR_Now() into milliseconds since epoch + // and salt rand with that. + static unsigned int seed = 0; + if (seed == 0) { + double fpTime = double(PR_Now()); + seed = + (unsigned int)(fpTime * 1e-6 + 0.5); // use 1e-6, granularity of + // PR_Now() on the mac is seconds + srand(seed); + } + + int32_t i; + for (i = 0; i < aBufLen; ++i) { + *aBuf++ = table[rand() % TABLE_SIZE]; + } + *aBuf = 0; +} + +#endif diff --git a/xpcom/base/nsCRTGlue.h b/xpcom/base/nsCRTGlue.h new file mode 100644 index 0000000000..2bfe47b2a8 --- /dev/null +++ b/xpcom/base/nsCRTGlue.h @@ -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/. */ + +#ifndef nsCRTGlue_h__ +#define nsCRTGlue_h__ + +#include "nscore.h" + +/** + * Scan a string for the first character that is *not* in a set of + * delimiters. If the string is only delimiter characters, the end of the + * string is returned. + * + * @param aDelims The set of delimiters (null-terminated) + * @param aStr The string to search (null-terminated) + */ +const char* NS_strspnp(const char* aDelims, const char* aStr); + +/** + * Tokenize a string. This function is similar to the strtok function in the + * C standard library, but it does not use static variables to maintain state + * and is therefore thread and reentrancy-safe. + * + * Any leading delimiters in str are skipped. Then the string is scanned + * until an additional delimiter or end-of-string is found. The final + * delimiter is set to '\0'. + * + * @param aDelims The set of delimiters. + * @param aStr The string to search. This is an in-out parameter; it is + * reset to the end of the found token + 1, or to the + * end-of-string if there are no more tokens. + * @return The token. If no token is found (the string is only + * delimiter characters), nullptr is returned. + */ +char* NS_strtok(const char* aDelims, char** aStr); + +/** + * "strlen" for char16_t strings + */ +uint32_t NS_strlen(const char16_t* aString); + +/** + * "strcmp" for char16_t strings + */ +int NS_strcmp(const char16_t* aStrA, const char16_t* aStrB); + +/** + * "strncmp" for char16_t strings + */ +int NS_strncmp(const char16_t* aStrA, const char16_t* aStrB, size_t aLen); + +/** + * "strdup" for char16_t strings, uses the infallible moz_xmalloc allocator. + */ +char16_t* NS_xstrdup(const char16_t* aString); + +/** + * "strdup", but using the infallible moz_xmalloc allocator. + */ +char* NS_xstrdup(const char* aString); + +/** + * strndup for char16_t or char strings (normal strndup is not available on + * windows). This function will ensure that the new string is + * null-terminated. Uses the infallible moz_xmalloc allocator. + * + * CharT may be either char16_t or char. + */ +template <typename CharT> +CharT* NS_xstrndup(const CharT* aString, uint32_t aLen); + +// The following case-conversion methods only deal in the ascii repertoire +// A-Z and a-z + +// semi-private data declarations... don't use these directly. +class nsLowerUpperUtils { + public: + static const unsigned char kLower2Upper[256]; + static const unsigned char kUpper2Lower[256]; +}; + +inline char NS_ToUpper(char aChar) { + return (char)nsLowerUpperUtils::kLower2Upper[(unsigned char)aChar]; +} + +inline char NS_ToLower(char aChar) { + return (char)nsLowerUpperUtils::kUpper2Lower[(unsigned char)aChar]; +} + +bool NS_IsUpper(char aChar); +bool NS_IsLower(char aChar); + +constexpr bool NS_IsAscii(const char* aString) { + while (*aString) { + if (0x80 & *aString) { + return false; + } + aString++; + } + return true; +} + +constexpr bool NS_IsAscii(const char* aString, uint32_t aLength) { + const char* end = aString + aLength; + while (aString < end) { + if (0x80 & *aString) { + return false; + } + aString++; + } + return true; +} + +constexpr bool NS_IsAsciiWhitespace(char16_t aChar) { + return aChar == ' ' || aChar == '\r' || aChar == '\n' || aChar == '\t'; +} + +#ifndef XPCOM_GLUE_AVOID_NSPR +void NS_MakeRandomString(char* aBuf, int32_t aBufLen); +#endif + +#define FF '\f' +#define TAB '\t' + +#define CRSTR "\015" +#define LFSTR "\012" +#define CRLF "\015\012" /* A CR LF equivalent string */ + +#if defined(ANDROID) +// On mobile devices, the file system may be very limited in what it +// considers valid characters. To avoid errors, sanitize conservatively. +# define OS_FILE_ILLEGAL_CHARACTERS "/:*?\"<>|;,+=[]" +#else +// Otherwise, we use the most restrictive filesystem as our default set of +// illegal filename characters. This is currently Windows. +# define OS_FILE_ILLEGAL_CHARACTERS "/:*?\"<>|" +#endif + +// We also provide a list of all known file path separators for all filesystems. +// This can be used in replacement of FILE_PATH_SEPARATOR when you need to +// identify or replace all known path separators. +#define KNOWN_PATH_SEPARATORS "\\/" + +#if defined(XP_MACOSX) +# define FILE_PATH_SEPARATOR "/" +#elif defined(XP_WIN) +# define FILE_PATH_SEPARATOR "\\" +#elif defined(XP_UNIX) +# define FILE_PATH_SEPARATOR "/" +#else +# error need_to_define_your_file_path_separator_and_maybe_illegal_characters +#endif + +// Not all these control characters are illegal in all OSs, but we don't really +// want them appearing in filenames +#define CONTROL_CHARACTERS \ + "\001\002\003\004\005\006\007" \ + "\010\011\012\013\014\015\016\017" \ + "\020\021\022\023\024\025\026\027" \ + "\030\031\032\033\034\035\036\037" \ + "\177" \ + "\200\201\202\203\204\205\206\207" \ + "\210\211\212\213\214\215\216\217" \ + "\220\221\222\223\224\225\226\227" \ + "\230\231\232\233\234\235\236\237" + +#define FILE_ILLEGAL_CHARACTERS CONTROL_CHARACTERS OS_FILE_ILLEGAL_CHARACTERS + +#endif // nsCRTGlue_h__ diff --git a/xpcom/base/nsClassInfoImpl.cpp b/xpcom/base/nsClassInfoImpl.cpp new file mode 100644 index 0000000000..2189863a98 --- /dev/null +++ b/xpcom/base/nsClassInfoImpl.cpp @@ -0,0 +1,61 @@ +/* -*- 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 "nsIClassInfoImpl.h" +#include "nsString.h" + +NS_IMETHODIMP_(MozExternalRefCountType) +GenericClassInfo::AddRef() { return 2; } + +NS_IMETHODIMP_(MozExternalRefCountType) +GenericClassInfo::Release() { return 1; } + +NS_IMPL_QUERY_INTERFACE(GenericClassInfo, nsIClassInfo) + +NS_IMETHODIMP +GenericClassInfo::GetInterfaces(nsTArray<nsIID>& aArray) { + return mData->getinterfaces(aArray); +} + +NS_IMETHODIMP +GenericClassInfo::GetScriptableHelper(nsIXPCScriptable** aHelper) { + if (mData->getscriptablehelper) { + return mData->getscriptablehelper(aHelper); + } + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +GenericClassInfo::GetContractID(nsACString& aContractID) { + NS_ERROR("GetContractID not implemented"); + aContractID.SetIsVoid(true); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +GenericClassInfo::GetClassDescription(nsACString& aDescription) { + aDescription.SetIsVoid(true); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +GenericClassInfo::GetClassID(nsCID** aClassID) { + NS_ERROR("GetClassID not implemented"); + *aClassID = nullptr; + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +GenericClassInfo::GetFlags(uint32_t* aFlags) { + *aFlags = mData->flags; + return NS_OK; +} + +NS_IMETHODIMP +GenericClassInfo::GetClassIDNoAlloc(nsCID* aClassIDNoAlloc) { + *aClassIDNoAlloc = mData->cid; + return NS_OK; +} diff --git a/xpcom/base/nsCom.h b/xpcom/base/nsCom.h new file mode 100644 index 0000000000..b6c3ed0296 --- /dev/null +++ b/xpcom/base/nsCom.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 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 0000000000..f856c9eac6 --- /dev/null +++ b/xpcom/base/nsConsoleMessage.cpp @@ -0,0 +1,67 @@ +/* -*- 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() : mMicroSecondTimeStamp(0) {} + +nsConsoleMessage::nsConsoleMessage(const nsAString& aMessage) { + mMicroSecondTimeStamp = JS_Now(); + mMessage.Assign(aMessage); + mIsForwardedFromContentProcess = false; +} + +NS_IMETHODIMP +nsConsoleMessage::GetMessageMoz(nsAString& aMessage) { + aMessage = 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 = mMicroSecondTimeStamp / 1000; + return NS_OK; +} + +NS_IMETHODIMP +nsConsoleMessage::GetMicroSecondTimeStamp(int64_t* aTimeStamp) { + *aTimeStamp = mMicroSecondTimeStamp; + return NS_OK; +} + +NS_IMETHODIMP +nsConsoleMessage::ToString(nsACString& /*UTF8*/ aResult) { + CopyUTF16toUTF8(mMessage, aResult); + + return NS_OK; +} + +NS_IMETHODIMP +nsConsoleMessage::GetIsForwardedFromContentProcess( + bool* aIsForwardedFromContentProcess) { + *aIsForwardedFromContentProcess = mIsForwardedFromContentProcess; + return NS_OK; +} + +NS_IMETHODIMP +nsConsoleMessage::SetIsForwardedFromContentProcess( + bool aIsForwardedFromContentProcess) { + mIsForwardedFromContentProcess = aIsForwardedFromContentProcess; + return NS_OK; +} diff --git a/xpcom/base/nsConsoleMessage.h b/xpcom/base/nsConsoleMessage.h new file mode 100644 index 0000000000..42509707ba --- /dev/null +++ b/xpcom/base/nsConsoleMessage.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/. */ + +#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 nsAString& aMessage); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSICONSOLEMESSAGE + + private: + ~nsConsoleMessage() = default; + + int64_t mMicroSecondTimeStamp; + nsString mMessage; + bool mIsForwardedFromContentProcess; +}; + +#endif /* __nsconsolemessage_h__ */ diff --git a/xpcom/base/nsConsoleService.cpp b/xpcom/base/nsConsoleService.cpp new file mode 100644 index 0000000000..e6b17a6571 --- /dev/null +++ b/xpcom/base/nsConsoleService.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/. */ + +/* + * Maintains a circular buffer of recent messages, and notifies + * listeners when new messages are logged. + */ + +/* Threadsafe. */ + +#include "nsCOMArray.h" +#include "nsThreadUtils.h" + +#include "nsConsoleService.h" +#include "nsConsoleMessage.h" +#include "nsIClassInfoImpl.h" +#include "nsIConsoleListener.h" +#include "nsIObserverService.h" +#include "nsPrintfCString.h" +#include "nsProxyRelease.h" +#include "nsIScriptError.h" +#include "nsISupportsPrimitives.h" +#include "js/friend/ErrorMessages.h" +#include "mozilla/dom/WindowGlobalParent.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/BrowserParent.h" +#include "mozilla/dom/ScriptSettings.h" + +#include "mozilla/SchedulerGroup.h" +#include "mozilla/Services.h" + +#if defined(ANDROID) +# include <android/log.h> +# include "mozilla/dom/ContentChild.h" +# include "mozilla/StaticPrefs_consoleservice.h" +#endif +#ifdef XP_WIN +# include <windows.h> +#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 const bool gLoggingEnabled = true; +static const bool gLoggingBuffered = true; +#ifdef XP_WIN +static bool gLoggingToDebugger = true; +#endif // XP_WIN + +nsConsoleService::MessageElement::~MessageElement() = default; + +nsConsoleService::nsConsoleService() + : mCurrentSize(0), + // XXX grab this from a pref! + // hm, but worry about circularity, bc we want to be able to report + // prefs errs... + mMaximumSize(250), + mDeliveringMessage(false), + mLock("nsConsoleService.mLock") { +#ifdef XP_WIN + // This environment variable controls whether the console service + // should be prevented from putting output to the attached debugger. + // It only affects the Windows platform. + // + // To disable OutputDebugString, set: + // MOZ_CONSOLESERVICE_DISABLE_DEBUGGER_OUTPUT=1 + // + const char* disableDebugLoggingVar = + getenv("MOZ_CONSOLESERVICE_DISABLE_DEBUGGER_OUTPUT"); + gLoggingToDebugger = + !disableDebugLoggingVar || (disableDebugLoggingVar[0] == '0'); +#endif // XP_WIN +} + +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) + : mozilla::Runnable("AddConsolePrefWatchers"), mConsole(aConsole) {} + + NS_IMETHOD Run() override { + 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 (!gLoggingBuffered) { + mConsole->Reset(); + } + return NS_OK; + } + + private: + RefPtr<nsConsoleService> mConsole; +}; + +nsresult nsConsoleService::Init() { + NS_DispatchToMainThread(new AddConsolePrefWatchers(this)); + + return NS_OK; +} + +nsresult nsConsoleService::MaybeForwardScriptError(nsIConsoleMessage* aMessage, + bool* sent) { + *sent = false; + + nsCOMPtr<nsIScriptError> scriptError = do_QueryInterface(aMessage); + if (!scriptError) { + // Not an nsIScriptError + return NS_OK; + } + + uint64_t windowID; + nsresult rv; + rv = scriptError->GetInnerWindowID(&windowID); + NS_ENSURE_SUCCESS(rv, rv); + if (!windowID) { + // Does not set window id + return NS_OK; + } + + RefPtr<mozilla::dom::WindowGlobalParent> windowGlobalParent = + mozilla::dom::WindowGlobalParent::GetByInnerWindowId(windowID); + if (!windowGlobalParent) { + // Could not find parent window by id + return NS_OK; + } + + RefPtr<mozilla::dom::BrowserParent> browserParent = + windowGlobalParent->GetBrowserParent(); + if (!browserParent) { + return NS_OK; + } + + mozilla::dom::ContentParent* contentParent = browserParent->Manager(); + if (!contentParent) { + return NS_ERROR_FAILURE; + } + + nsAutoString msg, sourceName, sourceLine; + nsCString category; + uint32_t lineNum, colNum, flags; + uint64_t innerWindowId; + bool fromPrivateWindow, fromChromeContext; + + rv = scriptError->GetErrorMessage(msg); + NS_ENSURE_SUCCESS(rv, rv); + rv = scriptError->GetSourceName(sourceName); + NS_ENSURE_SUCCESS(rv, rv); + rv = scriptError->GetSourceLine(sourceLine); + NS_ENSURE_SUCCESS(rv, rv); + + rv = scriptError->GetCategory(getter_Copies(category)); + NS_ENSURE_SUCCESS(rv, rv); + rv = scriptError->GetLineNumber(&lineNum); + NS_ENSURE_SUCCESS(rv, rv); + rv = scriptError->GetColumnNumber(&colNum); + NS_ENSURE_SUCCESS(rv, rv); + rv = scriptError->GetFlags(&flags); + NS_ENSURE_SUCCESS(rv, rv); + rv = scriptError->GetIsFromPrivateWindow(&fromPrivateWindow); + NS_ENSURE_SUCCESS(rv, rv); + rv = scriptError->GetIsFromChromeContext(&fromChromeContext); + NS_ENSURE_SUCCESS(rv, rv); + rv = scriptError->GetInnerWindowID(&innerWindowId); + NS_ENSURE_SUCCESS(rv, rv); + + *sent = contentParent->SendScriptError( + msg, sourceName, sourceLine, lineNum, colNum, flags, category, + fromPrivateWindow, innerWindowId, fromChromeContext); + return NS_OK; +} + +namespace { + +class LogMessageRunnable : public Runnable { + public: + LogMessageRunnable(nsIConsoleMessage* aMessage, nsConsoleService* aService) + : mozilla::Runnable("LogMessageRunnable"), + mMessage(aMessage), + mService(aService) {} + + NS_DECL_NSIRUNNABLE + + private: + nsCOMPtr<nsIConsoleMessage> mMessage; + RefPtr<nsConsoleService> mService; +}; + +NS_IMETHODIMP +LogMessageRunnable::Run() { + // 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, nsIConsoleService::OutputToLog); +} + +// This can be called off the main thread. +nsresult nsConsoleService::LogMessageWithMode( + nsIConsoleMessage* aMessage, nsIConsoleService::OutputMode aOutputMode) { + if (!aMessage) { + return NS_ERROR_INVALID_ARG; + } + + if (!gLoggingEnabled) { + 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; + } + + if (XRE_IsParentProcess() && NS_IsMainThread()) { + // If mMessage is a scriptError with an innerWindowId set, + // forward it to the matching ContentParent + // This enables logging from parent to content process + bool sent; + nsresult rv = MaybeForwardScriptError(aMessage, &sent); + NS_ENSURE_SUCCESS(rv, rv); + if (sent) { + return NS_OK; + } + } + + RefPtr<LogMessageRunnable> r; + nsCOMPtr<nsIConsoleMessage> retiredMessage; + + /* + * Lock while updating buffer, and while taking snapshot of + * listeners array. + */ + { + MutexAutoLock lock(mLock); + +#if defined(ANDROID) + if (StaticPrefs::consoleservice_logcat() && 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 (gLoggingToDebugger && IsDebuggerPresent()) { + nsString msg; + aMessage->GetMessageMoz(msg); + msg.Append('\n'); + OutputDebugStringW(msg.get()); + } +#endif + + if (gLoggingBuffered) { + 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("nsConsoleService::retiredMessage", + retiredMessage.forget()); + } + + if (r) { + // avoid failing in XPCShell tests + nsCOMPtr<nsIThread> mainThread = do_GetMainThread(); + if (mainThread) { + SchedulerGroup::Dispatch(r.forget()); + } + } + + return NS_OK; +} + +// See nsIConsoleService.idl for more info about this method +NS_IMETHODIMP +nsConsoleService::CallFunctionAndLogException( + JS::Handle<JS::Value> targetGlobal, JS::HandleValue function, JSContext* cx, + JS::MutableHandleValue retval) { + if (!targetGlobal.isObject() || !function.isObject()) { + return NS_ERROR_INVALID_ARG; + } + + JS::Rooted<JS::Realm*> contextRealm(cx, JS::GetCurrentRealmOrNull(cx)); + if (!contextRealm) { + return NS_ERROR_INVALID_ARG; + } + + JS::Rooted<JSObject*> global( + cx, js::CheckedUnwrapDynamic(&targetGlobal.toObject(), cx)); + if (!global) { + return NS_ERROR_INVALID_ARG; + } + + // Use AutoJSAPI in order to trigger AutoJSAPI::ReportException + // which will do most of the work required for this function. + // + // We only have to pick the right global for which we want to flag + // the exception against. + dom::AutoJSAPI jsapi; + if (!jsapi.Init(global)) { + return NS_ERROR_UNEXPECTED; + } + JSContext* ccx = jsapi.cx(); + + // AutoJSAPI picks `targetGlobal` as execution compartment + // whereas we expect to run `function` from the callsites compartment. + JSAutoRealm ar(ccx, JS::GetRealmGlobalOrNull(contextRealm)); + + JS::RootedValue funVal(ccx, function); + if (!JS_WrapValue(ccx, &funVal)) { + return NS_ERROR_FAILURE; + } + if (!JS_CallFunctionValue(ccx, nullptr, funVal, JS::HandleValueArray::empty(), + retval)) { + return NS_ERROR_XPC_JAVASCRIPT_ERROR; + } + + return NS_OK; +} + +void nsConsoleService::CollectCurrentListeners( + nsCOMArray<nsIConsoleListener>& aListeners) { + MutexAutoLock lock(mLock); + // XXX When MakeBackInserter(nsCOMArray<T>&) is added, we can do: + // AppendToArray(aListeners, mListeners.Values()); + for (const auto& listener : mListeners.Values()) { + aListeners.AppendObject(listener); + } +} + +NS_IMETHODIMP +nsConsoleService::LogStringMessage(const char16_t* aMessage) { + if (!gLoggingEnabled) { + return NS_OK; + } + + RefPtr<nsConsoleMessage> msg(new nsConsoleMessage( + aMessage ? nsDependentString(aMessage) : EmptyString())); + return LogMessage(msg); +} + +NS_IMETHODIMP +nsConsoleService::GetMessageArray( + nsTArray<RefPtr<nsIConsoleMessage>>& aMessages) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + + MutexAutoLock lock(mLock); + + if (mMessages.isEmpty()) { + return NS_OK; + } + + MOZ_ASSERT(mCurrentSize <= mMaximumSize); + aMessages.SetCapacity(mCurrentSize); + + for (MessageElement* e = mMessages.getFirst(); e != nullptr; + e = e->getNext()) { + aMessages.AppendElement(e->Get()); + } + + 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); + MOZ_ASSERT(canonical); + + MutexAutoLock lock(mLock); + return mListeners.WithEntryHandle(canonical, [&](auto&& entry) { + if (entry) { + // Reregistering a listener isn't good + return NS_ERROR_FAILURE; + } + entry.Insert(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); + + return mListeners.Remove(canonical) + ? NS_OK + // Unregistering a listener that was never registered? + : NS_ERROR_FAILURE; +} + +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::ResetWindow(uint64_t windowInnerId) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + + ClearMessagesForWindowID(windowInnerId); + 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 0000000000..fe86f4c0fe --- /dev/null +++ b/xpcom/base/nsConsoleService.h @@ -0,0 +1,113 @@ +/* -*- 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 <cstdint> +#include <utility> + +#include "mozilla/Assertions.h" +#include "mozilla/LinkedList.h" +#include "mozilla/Mutex.h" + +#include "MainThreadUtils.h" +#include "nsCOMPtr.h" +#include "nsInterfaceHashtable.h" +#include "nsHashKeys.h" + +#include "nsIConsoleListener.h" +#include "nsIConsoleMessage.h" +#include "nsIConsoleService.h" +#include "nsIObserver.h" +#include "nsISupports.h" + +template <class T> +class nsCOMArray; + +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; + } + + 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(); + + nsresult MaybeForwardScriptError(nsIConsoleMessage* aMessage, bool* sent); + + void ClearMessagesForWindowID(const uint64_t innerID); + void ClearMessages() MOZ_REQUIRES(mLock); + + mozilla::LinkedList<MessageElement> mMessages MOZ_GUARDED_BY(mLock); + + // The current size of mMessages. + uint32_t mCurrentSize MOZ_GUARDED_BY(mLock); + + // The maximum size of mMessages. + const 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. + // Only touched on MainThread + bool mDeliveringMessage; + + // Listeners to notify whenever a new message is logged. + ListenerHash mListeners MOZ_GUARDED_BY(mLock); + + // 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 0000000000..67436c15c4 --- /dev/null +++ b/xpcom/base/nsCrashOnException.cpp @@ -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/. */ + +#include "nsCrashOnException.h" +#include "nsCOMPtr.h" +#include "nsICrashReporter.h" +#include "nsServiceManagerUtils.h" + +namespace mozilla { + +static int ReportException(EXCEPTION_POINTERS* aExceptionInfo) { + nsCOMPtr<nsICrashReporter> cr = + do_GetService("@mozilla.org/toolkit/crash-reporter;1"); + if (cr) { + cr->WriteMinidumpForException(aExceptionInfo); + } + + 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 +} + +} // namespace mozilla diff --git a/xpcom/base/nsCrashOnException.h b/xpcom/base/nsCrashOnException.h new file mode 100644 index 0000000000..1291cd4961 --- /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); + +} // namespace mozilla + +#endif diff --git a/xpcom/base/nsCycleCollectionNoteChild.h b/xpcom/base/nsCycleCollectionNoteChild.h new file mode 100644 index 0000000000..3e1d345aa3 --- /dev/null +++ b/xpcom/base/nsCycleCollectionNoteChild.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/. */ + +// This header will be included by headers that define refpointer and array +// classes in order to specialize CC helpers such as ImplCycleCollectionTraverse +// for them. + +#ifndef nsCycleCollectionNoteChild_h__ +#define nsCycleCollectionNoteChild_h__ + +#include "nsCycleCollectionTraversalCallback.h" +#include "mozilla/Likely.h" + +enum { CycleCollectionEdgeNameArrayFlag = 1 }; + +// Just a helper for appending "[i]". Didn't want to pull in string headers +// here. +void CycleCollectionNoteEdgeNameImpl( + nsCycleCollectionTraversalCallback& aCallback, const char* aName, + uint32_t aFlags = 0); + +// Should be inlined so that in the no-debug-info case this is just a simple +// if(). +MOZ_ALWAYS_INLINE void CycleCollectionNoteEdgeName( + nsCycleCollectionTraversalCallback& aCallback, const char* aName, + uint32_t aFlags = 0) { + if (MOZ_UNLIKELY(aCallback.WantDebugInfo())) { + CycleCollectionNoteEdgeNameImpl(aCallback, aName, aFlags); + } +} + +#define NS_CYCLE_COLLECTION_INNERCLASS cycleCollection + +#define NS_CYCLE_COLLECTION_INNERNAME _cycleCollectorGlobal + +#define NS_CYCLE_COLLECTION_PARTICIPANT(_class) \ + _class::NS_CYCLE_COLLECTION_INNERCLASS::GetParticipant() + +template <typename T> +nsISupports* ToSupports( + T* aPtr, typename T::NS_CYCLE_COLLECTION_INNERCLASS* aDummy = 0) { + return T::NS_CYCLE_COLLECTION_INNERCLASS::Upcast(aPtr); +} + +// The default implementation of this class template is empty, because it +// should never be used: see the partial specializations below. +template <typename T, bool IsXPCOM = std::is_base_of<nsISupports, T>::value> +struct CycleCollectionNoteChildImpl {}; + +template <typename T> +struct CycleCollectionNoteChildImpl<T, true> { + static void Run(nsCycleCollectionTraversalCallback& aCallback, T* aChild) { + aCallback.NoteXPCOMChild(ToSupports(aChild)); + } +}; + +template <typename T> +struct CycleCollectionNoteChildImpl<T, false> { + static void Run(nsCycleCollectionTraversalCallback& aCallback, T* aChild) { + aCallback.NoteNativeChild(aChild, NS_CYCLE_COLLECTION_PARTICIPANT(T)); + } +}; + +// We declare CycleCollectionNoteChild in 3-argument and 4-argument variants, +// rather than using default arguments, so that forward declarations work +// regardless of header inclusion order. +template <typename T> +inline void CycleCollectionNoteChild( + nsCycleCollectionTraversalCallback& aCallback, T* aChild, const char* aName, + uint32_t aFlags) { + CycleCollectionNoteEdgeName(aCallback, aName, aFlags); + CycleCollectionNoteChildImpl<T>::Run(aCallback, aChild); +} + +template <typename T> +inline void CycleCollectionNoteChild( + nsCycleCollectionTraversalCallback& aCallback, T* aChild, + const char* aName) { + CycleCollectionNoteChild(aCallback, aChild, aName, 0); +} + +#endif // nsCycleCollectionNoteChild_h__ diff --git a/xpcom/base/nsCycleCollectionNoteRootCallback.h b/xpcom/base/nsCycleCollectionNoteRootCallback.h new file mode 100644 index 0000000000..21dc89394a --- /dev/null +++ b/xpcom/base/nsCycleCollectionNoteRootCallback.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 nsCycleCollectionNoteRootCallback_h__ +#define nsCycleCollectionNoteRootCallback_h__ + +#include "nscore.h" + +class nsCycleCollectionParticipant; +class nsISupports; +class JSObject; + +namespace JS { +class GCCellPtr; +} + +class nsCycleCollectionNoteRootCallback { + public: + // aRoot must be canonical (ie the result of QIing to + // nsCycleCollectionISupports). + NS_IMETHOD_(void) + NoteXPCOMRoot(nsISupports* aRoot, + nsCycleCollectionParticipant* aParticipant) = 0; + + NS_IMETHOD_(void) NoteJSRoot(JSObject* aRoot) = 0; + NS_IMETHOD_(void) + NoteNativeRoot(void* aRoot, nsCycleCollectionParticipant* aParticipant) = 0; + + NS_IMETHOD_(void) + NoteWeakMapping(JSObject* aMap, JS::GCCellPtr aKey, JSObject* aKeyDelegate, + JS::GCCellPtr aVal) = 0; + + bool WantAllTraces() const { return mWantAllTraces; } + + protected: + nsCycleCollectionNoteRootCallback() : mWantAllTraces(false) {} + + bool mWantAllTraces; +}; + +#endif // nsCycleCollectionNoteRootCallback_h__ diff --git a/xpcom/base/nsCycleCollectionParticipant.cpp b/xpcom/base/nsCycleCollectionParticipant.cpp new file mode 100644 index 0000000000..eaca656e23 --- /dev/null +++ b/xpcom/base/nsCycleCollectionParticipant.cpp @@ -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/. */ + +#include "nsCycleCollectionParticipant.h" +#include "nsCOMPtr.h" + +NS_IMETHODIMP_(void) +nsXPCOMCycleCollectionParticipant::Root(void* aPtr) { + nsISupports* s = static_cast<nsISupports*>(aPtr); + NS_ADDREF(s); +} + +NS_IMETHODIMP_(void) +nsXPCOMCycleCollectionParticipant::Unroot(void* aPtr) { + nsISupports* s = static_cast<nsISupports*>(aPtr); + NS_RELEASE(s); +} + +// We define a default trace function because some participants don't need +// to trace anything, so it is okay for them not to define one. +NS_IMETHODIMP_(void) +nsXPCOMCycleCollectionParticipant::Trace(void* aPtr, const TraceCallbacks& aCb, + void* aClosure) {} + +bool nsXPCOMCycleCollectionParticipant::CheckForRightISupports( + nsISupports* aSupports) { + nsISupports* foo; + aSupports->QueryInterface(NS_GET_IID(nsCycleCollectionISupports), + reinterpret_cast<void**>(&foo)); + return aSupports == foo; +} diff --git a/xpcom/base/nsCycleCollectionParticipant.h b/xpcom/base/nsCycleCollectionParticipant.h new file mode 100644 index 0000000000..e4d293f42e --- /dev/null +++ b/xpcom/base/nsCycleCollectionParticipant.h @@ -0,0 +1,1107 @@ +/* -*- 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 nsCycleCollectionParticipant_h__ +#define nsCycleCollectionParticipant_h__ + +#include <type_traits> +#include "js/HeapAPI.h" +#include "js/TypeDecls.h" +#include "mozilla/MacroForEach.h" +#include "nsCycleCollectionNoteChild.h" +#include "nsDebug.h" +#include "nsID.h" +#include "nscore.h" + +/** + * Note: the following two IIDs only differ in one bit in the last byte. This + * is a hack and is intentional in order to speed up the comparison inside + * NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED. + */ +#define NS_XPCOMCYCLECOLLECTIONPARTICIPANT_IID \ + { \ + 0xc61eac14, 0x5f7a, 0x4481, { \ + 0x96, 0x5e, 0x7e, 0xaa, 0x6e, 0xff, 0xa8, 0x5e \ + } \ + } + +/** + * Special IID to get at the base nsISupports for a class. Usually this is the + * canonical nsISupports pointer, but in the case of tearoffs for example it is + * the base nsISupports pointer of the tearoff. This allow the cycle collector + * to have separate nsCycleCollectionParticipant's for tearoffs or aggregated + * classes. + */ +#define NS_CYCLECOLLECTIONISUPPORTS_IID \ + { \ + 0xc61eac14, 0x5f7a, 0x4481, { \ + 0x96, 0x5e, 0x7e, 0xaa, 0x6e, 0xff, 0xa8, 0x5f \ + } \ + } + +namespace mozilla { +enum class CCReason : uint8_t { + NO_REASON, + + // Purple buffer "overflow": enough objects are suspected to be cycle + // collectable to trigger an immediate CC. + MANY_SUSPECTED, + + // The previous collection was kCCForced ago, and there are at least + // kCCForcedPurpleLimit objects suspected of being cycle collectable. + TIMED, + + // Run a CC after a GC has completed. + GC_FINISHED, + + // Run a slice of a collection. + SLICE, + + // Manual reasons are explicitly triggered with a custom listener (as opposed + // to exceeding some internal threshold.) If a CC is already in progress, + // continue it. Otherwise, start a new one. + FIRST_MANUAL_REASON = 128, + + // We want to GC, but must finish any ongoing cycle collection first. + GC_WAITING = FIRST_MANUAL_REASON, + + // CC requested via an API. Used by tests. + API, + + // Collecting in order to dump the heap. + DUMP_HEAP, + + // Low memory situation detected. + MEM_PRESSURE, + + // IPC message to a content process to trigger a CC. The original reason is + // not tracked. + IPC_MESSAGE, + + // Cycle collection on a worker. The triggering reason is not tracked. + WORKER, + + // Used for finding leaks. + SHUTDOWN +}; + +#define FOR_EACH_CCREASON(MACRO) \ + MACRO(NO_REASON) \ + MACRO(MANY_SUSPECTED) \ + MACRO(TIMED) \ + MACRO(GC_FINISHED) \ + MACRO(SLICE) \ + MACRO(GC_WAITING) \ + MACRO(API) \ + MACRO(DUMP_HEAP) \ + MACRO(MEM_PRESSURE) \ + MACRO(IPC_MESSAGE) \ + MACRO(WORKER) \ + MACRO(SHUTDOWN) + +static inline bool IsManualCCReason(CCReason reason) { + return reason >= CCReason::FIRST_MANUAL_REASON; +} + +static inline const char* CCReasonToString(CCReason aReason) { + switch (aReason) { +#define SET_REASON_STR(name) \ + case CCReason::name: \ + return #name; \ + break; + FOR_EACH_CCREASON(SET_REASON_STR) +#undef SET_REASON_STR + default: + return "<unknown-reason>"; + } +} + +} // namespace mozilla + +/** + * Just holds the IID so NS_GET_IID works. + */ +class nsCycleCollectionISupports { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_CYCLECOLLECTIONISUPPORTS_IID) +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsCycleCollectionISupports, + NS_CYCLECOLLECTIONISUPPORTS_IID) + +class nsCycleCollectionTraversalCallback; +class nsISupports; +class nsWrapperCache; + +namespace JS { +template <class T> +class Heap; +template <typename T> +class TenuredHeap; +} /* namespace JS */ + +/* + * A struct defining pure virtual methods which are called when tracing cycle + * collection paticipants. The appropriate method is called depending on the + * type of JS GC thing. + */ +struct TraceCallbacks { + virtual void Trace(JS::Heap<JS::Value>* aPtr, const char* aName, + void* aClosure) const = 0; + virtual void Trace(JS::Heap<jsid>* aPtr, const char* aName, + void* aClosure) const = 0; + virtual void Trace(JS::Heap<JSObject*>* aPtr, const char* aName, + void* aClosure) const = 0; + virtual void Trace(nsWrapperCache* aPtr, const char* aName, + void* aClosure) const = 0; + virtual void Trace(JS::TenuredHeap<JSObject*>* aPtr, const char* aName, + void* aClosure) const = 0; + virtual void Trace(JS::Heap<JSString*>* aPtr, const char* aName, + void* aClosure) const = 0; + virtual void Trace(JS::Heap<JSScript*>* aPtr, const char* aName, + void* aClosure) const = 0; + virtual void Trace(JS::Heap<JSFunction*>* aPtr, const char* aName, + void* aClosure) const = 0; +}; + +/* + * An implementation of TraceCallbacks that calls a single function for all JS + * GC thing types encountered. Implemented in + * nsCycleCollectorTraceJSHelpers.cpp. + */ +struct TraceCallbackFunc : public TraceCallbacks { + typedef void (*Func)(JS::GCCellPtr aPtr, const char* aName, void* aClosure); + + explicit TraceCallbackFunc(Func aCb) : mCallback(aCb) {} + + virtual void Trace(JS::Heap<JS::Value>* aPtr, const char* aName, + void* aClosure) const override; + virtual void Trace(JS::Heap<jsid>* aPtr, const char* aName, + void* aClosure) const override; + virtual void Trace(JS::Heap<JSObject*>* aPtr, const char* aName, + void* aClosure) const override; + virtual void Trace(nsWrapperCache* aPtr, const char* aName, + void* aClosure) const override; + virtual void Trace(JS::TenuredHeap<JSObject*>* aPtr, const char* aName, + void* aClosure) const override; + virtual void Trace(JS::Heap<JSString*>* aPtr, const char* aName, + void* aClosure) const override; + virtual void Trace(JS::Heap<JSScript*>* aPtr, const char* aName, + void* aClosure) const override; + virtual void Trace(JS::Heap<JSFunction*>* aPtr, const char* aName, + void* aClosure) const override; + + private: + Func mCallback; +}; + +/** + * Participant implementation classes + */ +class NS_NO_VTABLE nsCycleCollectionParticipant { + public: + using Flags = uint8_t; + static constexpr Flags FlagMightSkip = 1u << 0; + static constexpr Flags FlagTraverseShouldTrace = 1u << 1; + // The object is a single zone js holder if FlagMaybeSingleZoneJSHolder is set + // and FlagMultiZoneJSHolder isn't set. This setup is needed so that + // inheriting classes can unset single zone behavior. + static constexpr Flags FlagMaybeSingleZoneJSHolder = 1u << 2; + static constexpr Flags FlagMultiZoneJSHolder = 1u << 3; + static constexpr Flags AllFlags = FlagMightSkip | FlagTraverseShouldTrace | + FlagMaybeSingleZoneJSHolder | + FlagMultiZoneJSHolder; + + constexpr explicit nsCycleCollectionParticipant(Flags aFlags) + : mFlags(aFlags) { + MOZ_ASSERT((aFlags & ~AllFlags) == 0); + } + + NS_IMETHOD TraverseNative(void* aPtr, + nsCycleCollectionTraversalCallback& aCb) = 0; + + nsresult TraverseNativeAndJS(void* aPtr, + nsCycleCollectionTraversalCallback& aCb) { + nsresult rv = TraverseNative(aPtr, aCb); + if (TraverseShouldTrace()) { + // Note, we always call Trace, even if Traverse returned + // NS_SUCCESS_INTERRUPTED_TRAVERSE. + TraceCallbackFunc noteJsChild(&nsCycleCollectionParticipant::NoteJSChild); + Trace(aPtr, noteJsChild, &aCb); + } + return rv; + } + + // Implemented in nsCycleCollectorTraceJSHelpers.cpp. + static void NoteJSChild(JS::GCCellPtr aGCThing, const char* aName, + void* aClosure); + + NS_IMETHOD_(void) Root(void* aPtr) = 0; + NS_IMETHOD_(void) Unlink(void* aPtr) = 0; + NS_IMETHOD_(void) Unroot(void* aPtr) = 0; + NS_IMETHOD_(const char*) ClassName() = 0; + + NS_IMETHOD_(void) + Trace(void* aPtr, const TraceCallbacks& aCb, void* aClosure) {} + + // CanSkip is called during nsCycleCollector_forgetSkippable. If it returns + // true, aPtr is removed from the purple buffer and therefore might be left + // out from the cycle collector graph the next time that's constructed (unless + // it's reachable in some other way). + // + // CanSkip is allowed to expand the set of certainly-alive objects by removing + // other objects from the purple buffer, marking JS things black (in the GC + // sense), and so forth. Furthermore, if aRemovingAllowed is true, this call + // is allowed to remove aPtr itself from the purple buffer. + // + // Things can return true from CanSkip if either they know they have no + // outgoing edges at all in the cycle collection graph (because then they + // can't be parts of a cycle) or they know for sure they're alive. + bool CanSkip(void* aPtr, bool aRemovingAllowed) { + return MightSkip() ? CanSkipReal(aPtr, aRemovingAllowed) : false; + } + + // CanSkipInCC is called during construction of the initial set of roots for + // the cycle collector graph. If it returns true, aPtr is left out of that + // set of roots. Note that the set of roots includes whatever is in the + // purple buffer (after earlier CanSkip calls) plus various other sources of + // roots, so an object can end up having CanSkipInCC called on it even if it + // returned true from CanSkip. One example of this would be an object that + // can potentially trace JS things. + // + // CanSkipInCC is allowed to remove other objects from the purple buffer but + // should not remove aPtr and should not mark JS things black. It should also + // not modify any reference counts. + // + // Things can return true from CanSkipInCC if either they know they have no + // outgoing edges at all in the cycle collection graph or they know for sure + // they're alive _and_ none of their outgoing edges are to gray (in the GC + // sense) gcthings. See also nsWrapperCache::HasNothingToTrace and + // nsWrapperCache::HasKnownLiveWrapperAndDoesNotNeedTracing. The restriction + // on not having outgoing edges to gray gcthings is because if we _do_ have + // them that means we have a "strong" edge to a JS thing and since we're alive + // we need to trace through it and mark keep them alive. Outgoing edges to + // C++ things don't matter here, because the criteria for when a CC + // participant is considered alive are slightly different for JS and C++ + // things: JS things are only considered alive when reachable via an edge from + // a live thing, while C++ things are also considered alive when their + // refcount exceeds the number of edges via which they are reachable. + bool CanSkipInCC(void* aPtr) { + return MightSkip() ? CanSkipInCCReal(aPtr) : false; + } + + // CanSkipThis is called during construction of the cycle collector graph, + // when we traverse an edge to aPtr and consider adding it to the graph. If + // it returns true, aPtr is not added to the graph. + // + // CanSkipThis is not allowed to change the liveness or reference count of any + // objects. + // + // Things can return true from CanSkipThis if either they know they have no + // outgoing edges at all in the cycle collection graph or they know for sure + // they're alive. + // + // Note that CanSkipThis doesn't have to worry about outgoing edges to gray GC + // things, because if this object could have those it already got added to the + // graph during root set construction. An object should never have + // CanSkipThis called on it if it has outgoing strong references to JS things. + bool CanSkipThis(void* aPtr) { + return MightSkip() ? CanSkipThisReal(aPtr) : false; + } + + NS_IMETHOD_(void) DeleteCycleCollectable(void* aPtr) = 0; + + bool IsSingleZoneJSHolder() const { + return (mFlags & FlagMaybeSingleZoneJSHolder) && + !(mFlags & FlagMultiZoneJSHolder); + } + + protected: + NS_IMETHOD_(bool) CanSkipReal(void* aPtr, bool aRemovingAllowed) { + NS_ASSERTION(false, "Forgot to implement CanSkipReal?"); + return false; + } + NS_IMETHOD_(bool) CanSkipInCCReal(void* aPtr) { + NS_ASSERTION(false, "Forgot to implement CanSkipInCCReal?"); + return false; + } + NS_IMETHOD_(bool) CanSkipThisReal(void* aPtr) { + NS_ASSERTION(false, "Forgot to implement CanSkipThisReal?"); + return false; + } + + private: + bool MightSkip() const { return mFlags & FlagMightSkip; } + bool TraverseShouldTrace() const { return mFlags & FlagTraverseShouldTrace; } + + const Flags mFlags; +}; + +class NS_NO_VTABLE nsScriptObjectTracer : public nsCycleCollectionParticipant { + public: + constexpr explicit nsScriptObjectTracer(Flags aFlags) + : nsCycleCollectionParticipant(aFlags | FlagTraverseShouldTrace) {} + + NS_IMETHOD_(void) + Trace(void* aPtr, const TraceCallbacks& aCb, void* aClosure) override = 0; +}; + +class NS_NO_VTABLE nsXPCOMCycleCollectionParticipant + : public nsScriptObjectTracer { + public: + constexpr explicit nsXPCOMCycleCollectionParticipant(Flags aFlags) + : nsScriptObjectTracer(aFlags) {} + + NS_DECLARE_STATIC_IID_ACCESSOR(NS_XPCOMCYCLECOLLECTIONPARTICIPANT_IID) + + NS_IMETHOD_(void) Root(void* aPtr) override; + NS_IMETHOD_(void) Unroot(void* aPtr) override; + + NS_IMETHOD_(void) + Trace(void* aPtr, const TraceCallbacks& aCb, void* aClosure) override; + + static bool CheckForRightISupports(nsISupports* aSupports); +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsXPCOMCycleCollectionParticipant, + NS_XPCOMCYCLECOLLECTIONPARTICIPANT_IID) + +/////////////////////////////////////////////////////////////////////////////// +// Helpers for implementing a QI to nsXPCOMCycleCollectionParticipant +/////////////////////////////////////////////////////////////////////////////// + +#define NS_CYCLE_COLLECTION_CLASSNAME(_class) \ + _class::NS_CYCLE_COLLECTION_INNERCLASS + +// The IIDs for nsXPCOMCycleCollectionParticipant and nsCycleCollectionISupports +// are special in that they only differ in their last byte. This allows for the +// optimization below where we first check the first three words of the IID and +// if we find a match we check the last word to decide which case we have to +// deal with. +#define NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(_class) \ + if (TopThreeWordsEquals( \ + aIID, NS_GET_IID(nsXPCOMCycleCollectionParticipant), \ + NS_GET_IID( \ + nsCycleCollectionISupports)) && /* The calls to LowWordEquals \ + here are repeated inside the \ + if branch. This is due to the \ + fact that we need to maintain \ + the if/else chain for these \ + macros, so that the control \ + flow never enters the if \ + branch unless if we're \ + certain one of the \ + LowWordEquals() branches will \ + get executed. */ \ + (LowWordEquals(aIID, NS_GET_IID(nsXPCOMCycleCollectionParticipant)) || \ + LowWordEquals(aIID, NS_GET_IID(nsCycleCollectionISupports)))) { \ + if (LowWordEquals(aIID, NS_GET_IID(nsXPCOMCycleCollectionParticipant))) { \ + *aInstancePtr = NS_CYCLE_COLLECTION_PARTICIPANT(_class); \ + return NS_OK; \ + } \ + if (LowWordEquals(aIID, NS_GET_IID(nsCycleCollectionISupports))) { \ + *aInstancePtr = NS_CYCLE_COLLECTION_CLASSNAME(_class)::Upcast(this); \ + return NS_OK; \ + } \ + /* Avoid warnings about foundInterface being left uninitialized. */ \ + foundInterface = nullptr; \ + } else + +#define NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(_class) \ + NS_INTERFACE_MAP_BEGIN(_class) \ + NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(_class) + +#define NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(_class) \ + if (rv == NS_OK) return rv; \ + nsISupports* foundInterface; \ + NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(_class) + +// The IIDs for nsXPCOMCycleCollectionParticipant and nsCycleCollectionISupports +// are special in that they only differ in their last byte. This allows for the +// optimization below where we first check the first three words of the IID and +// if we find a match we check the last word to decide which case we have to +// deal with. +#define NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(_class) \ + NS_IMETHODIMP _class::QueryInterface(REFNSIID aIID, void** aInstancePtr) { \ + MOZ_ASSERT(aInstancePtr, "null out param"); \ + \ + if (TopThreeWordsEquals(aIID, \ + NS_GET_IID(nsXPCOMCycleCollectionParticipant), \ + NS_GET_IID(nsCycleCollectionISupports))) { \ + if (LowWordEquals(aIID, \ + NS_GET_IID(nsXPCOMCycleCollectionParticipant))) { \ + *aInstancePtr = NS_CYCLE_COLLECTION_PARTICIPANT(_class); \ + return NS_OK; \ + } \ + if (LowWordEquals(aIID, NS_GET_IID(nsCycleCollectionISupports))) { \ + *aInstancePtr = NS_CYCLE_COLLECTION_CLASSNAME(_class)::Upcast(this); \ + return NS_OK; \ + } \ + } \ + nsresult rv = NS_ERROR_FAILURE; + +#define NS_CYCLE_COLLECTION_UPCAST(obj, clazz) \ + NS_CYCLE_COLLECTION_CLASSNAME(clazz)::Upcast(obj) + +#ifdef DEBUG +# define NS_CHECK_FOR_RIGHT_PARTICIPANT(_ptr) _ptr->CheckForRightParticipant() +#else +# define NS_CHECK_FOR_RIGHT_PARTICIPANT(_ptr) +#endif + +// The default implementation of this class template is empty, because it +// should never be used: see the partial specializations below. +template <typename T, bool IsXPCOM = std::is_base_of<nsISupports, T>::value> +struct DowncastCCParticipantImpl {}; + +// Specialization for XPCOM CC participants +template <typename T> +struct DowncastCCParticipantImpl<T, true> { + static T* Run(void* aPtr) { + nsISupports* s = static_cast<nsISupports*>(aPtr); + MOZ_ASSERT(NS_CYCLE_COLLECTION_CLASSNAME(T)::CheckForRightISupports(s), + "not the nsISupports pointer we expect"); + T* rval = NS_CYCLE_COLLECTION_CLASSNAME(T)::Downcast(s); + NS_CHECK_FOR_RIGHT_PARTICIPANT(rval); + return rval; + } +}; + +// Specialization for native CC participants +template <typename T> +struct DowncastCCParticipantImpl<T, false> { + static T* Run(void* aPtr) { return static_cast<T*>(aPtr); } +}; + +template <typename T> +T* DowncastCCParticipant(void* aPtr) { + return DowncastCCParticipantImpl<T>::Run(aPtr); +} + +/////////////////////////////////////////////////////////////////////////////// +// Helpers for implementing CanSkip methods +/////////////////////////////////////////////////////////////////////////////// + +// See documentation for nsCycleCollectionParticipant::CanSkip for documentation +// about this method. +#define NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(_class) \ + NS_IMETHODIMP_(bool) \ + NS_CYCLE_COLLECTION_CLASSNAME(_class)::CanSkipReal(void* p, \ + bool aRemovingAllowed) { \ + _class* tmp = DowncastCCParticipant<_class>(p); + +#define NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END \ + (void)tmp; \ + return false; \ + } + +// See documentation for nsCycleCollectionParticipant::CanSkipInCC for +// documentation about this method. +#define NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(_class) \ + NS_IMETHODIMP_(bool) \ + NS_CYCLE_COLLECTION_CLASSNAME(_class)::CanSkipInCCReal(void* p) { \ + _class* tmp = DowncastCCParticipant<_class>(p); + +#define NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END \ + (void)tmp; \ + return false; \ + } + +// See documentation for nsCycleCollectionParticipant::CanSkipThis for +// documentation about this method. +#define NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(_class) \ + NS_IMETHODIMP_(bool) \ + NS_CYCLE_COLLECTION_CLASSNAME(_class)::CanSkipThisReal(void* p) { \ + _class* tmp = DowncastCCParticipant<_class>(p); + +#define NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END \ + (void)tmp; \ + return false; \ + } + +/////////////////////////////////////////////////////////////////////////////// +// Helpers for implementing nsCycleCollectionParticipant::Unlink +// +// You need to use NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED if you want +// the base class Unlink version to be called before your own implementation. +// You can use NS_IMPL_CYCLE_COLLECTION_UNLINK_END_INHERITED if you want the +// base class Unlink to get called after your own implementation. You should +// never use them together. +/////////////////////////////////////////////////////////////////////////////// + +#define NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(_class) \ + NS_IMETHODIMP_(void) \ + NS_CYCLE_COLLECTION_CLASSNAME(_class)::Unlink(void* p) { \ + _class* tmp = DowncastCCParticipant<_class>(p); + +#define NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(_class, _base_class) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(_class) \ + nsISupports* s = static_cast<nsISupports*>(p); \ + NS_CYCLE_COLLECTION_CLASSNAME(_base_class)::Unlink(s); + +#define NS_IMPL_CYCLE_COLLECTION_UNLINK_HELPER(_field) \ + ImplCycleCollectionUnlink(tmp->_field); + +#define NS_IMPL_CYCLE_COLLECTION_UNLINK(...) \ + MOZ_FOR_EACH(NS_IMPL_CYCLE_COLLECTION_UNLINK_HELPER, (), (__VA_ARGS__)) + +#define NS_IMPL_CYCLE_COLLECTION_UNLINK_END \ + (void)tmp; \ + } + +#define NS_IMPL_CYCLE_COLLECTION_UNLINK_END_INHERITED(_base_class) \ + nsISupports* s = static_cast<nsISupports*>(p); \ + NS_CYCLE_COLLECTION_CLASSNAME(_base_class)::Unlink(s); \ + (void)tmp; \ + } + +#define NS_IMPL_CYCLE_COLLECTION_UNLINK_0(_class) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(_class) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +/////////////////////////////////////////////////////////////////////////////// +// Helpers for implementing nsCycleCollectionParticipant::Traverse +/////////////////////////////////////////////////////////////////////////////// + +#define NS_IMPL_CYCLE_COLLECTION_DESCRIBE(_class, _refcnt) \ + cb.DescribeRefCountedNode(_refcnt, #_class); + +#define NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(_class) \ + NS_IMETHODIMP \ + NS_CYCLE_COLLECTION_CLASSNAME(_class)::TraverseNative( \ + void* p, nsCycleCollectionTraversalCallback& cb) { \ + _class* tmp = DowncastCCParticipant<_class>(p); + +#define NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(_class) \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(_class) \ + NS_IMPL_CYCLE_COLLECTION_DESCRIBE(_class, tmp->mRefCnt.get()) + +// Base class' CC participant should return NS_SUCCESS_INTERRUPTED_TRAVERSE +// from Traverse if it wants derived classes to not traverse anything from +// their CC participant. + +#define NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(_class, _base_class) \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(_class) \ + nsISupports* s = static_cast<nsISupports*>(p); \ + if (NS_CYCLE_COLLECTION_CLASSNAME(_base_class)::TraverseNative(s, cb) == \ + NS_SUCCESS_INTERRUPTED_TRAVERSE) { \ + return NS_SUCCESS_INTERRUPTED_TRAVERSE; \ + } + +#define NS_IMPL_CYCLE_COLLECTION_TRAVERSE_HELPER(_field) \ + ImplCycleCollectionTraverse(cb, tmp->_field, #_field, 0); + +#define NS_IMPL_CYCLE_COLLECTION_TRAVERSE(...) \ + MOZ_FOR_EACH(NS_IMPL_CYCLE_COLLECTION_TRAVERSE_HELPER, (), (__VA_ARGS__)) + +#define NS_IMPL_CYCLE_COLLECTION_TRAVERSE_RAWPTR(_field) \ + CycleCollectionNoteChild(cb, tmp->_field, #_field); + +#define NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END \ + (void)tmp; \ + return NS_OK; \ + } + +/////////////////////////////////////////////////////////////////////////////// +// Helpers for implementing nsScriptObjectTracer::Trace +/////////////////////////////////////////////////////////////////////////////// + +#define NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(_class) \ + void NS_CYCLE_COLLECTION_CLASSNAME(_class)::Trace( \ + void* p, const TraceCallbacks& aCallbacks, void* aClosure) { \ + _class* tmp = DowncastCCParticipant<_class>(p); + +#define NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(_class, _base_class) \ + NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(_class) \ + nsISupports* s = static_cast<nsISupports*>(p); \ + NS_CYCLE_COLLECTION_CLASSNAME(_base_class)::Trace(s, aCallbacks, aClosure); + +#define NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(_field) \ + aCallbacks.Trace(&tmp->_field, #_field, aClosure); + +// NB: The (void)tmp; hack in the TRACE_END macro exists to support +// implementations that don't need to do anything in their Trace method. +// Without this hack, some compilers warn about the unused tmp local. +#define NS_IMPL_CYCLE_COLLECTION_TRACE_END \ + (void)tmp; \ + } + +/////////////////////////////////////////////////////////////////////////////// +// Helpers for implementing a concrete nsCycleCollectionParticipant +/////////////////////////////////////////////////////////////////////////////// + +// If a class defines a participant, then QIing an instance of that class to +// nsXPCOMCycleCollectionParticipant should produce that participant. +#ifdef DEBUG +# define NS_CHECK_FOR_RIGHT_PARTICIPANT_BASE \ + virtual void CheckForRightParticipant() +# define NS_CHECK_FOR_RIGHT_PARTICIPANT_DERIVED \ + virtual void CheckForRightParticipant() override +# define NS_CHECK_FOR_RIGHT_PARTICIPANT_BODY(_class) \ + { \ + nsXPCOMCycleCollectionParticipant* p; \ + CallQueryInterface(this, &p); \ + MOZ_ASSERT(p == &NS_CYCLE_COLLECTION_INNERNAME, \ + #_class " should QI to its own CC participant"); \ + } +# define NS_CHECK_FOR_RIGHT_PARTICIPANT_IMPL(_class) \ + NS_CHECK_FOR_RIGHT_PARTICIPANT_BASE \ + NS_CHECK_FOR_RIGHT_PARTICIPANT_BODY(_class) +# define NS_CHECK_FOR_RIGHT_PARTICIPANT_IMPL_INHERITED(_class) \ + NS_CHECK_FOR_RIGHT_PARTICIPANT_DERIVED \ + NS_CHECK_FOR_RIGHT_PARTICIPANT_BODY(_class) +#else +# define NS_CHECK_FOR_RIGHT_PARTICIPANT_IMPL(_class) +# define NS_CHECK_FOR_RIGHT_PARTICIPANT_IMPL_INHERITED(_class) +#endif + +#define NS_DECL_CYCLE_COLLECTION_CLASS_NAME_METHOD(_class) \ + NS_IMETHOD_(const char*) ClassName() override { return #_class; }; + +#define NS_DECL_CYCLE_COLLECTION_CLASS_BODY(_class, _base) \ + public: \ + NS_IMETHOD TraverseNative(void* p, nsCycleCollectionTraversalCallback& cb) \ + override; \ + NS_DECL_CYCLE_COLLECTION_CLASS_NAME_METHOD(_class) \ + NS_IMETHOD_(void) DeleteCycleCollectable(void* p) override { \ + DowncastCCParticipant<_class>(p)->DeleteCycleCollectable(); \ + } \ + static _class* Downcast(nsISupports* s) { \ + return static_cast<_class*>(static_cast<_base*>(s)); \ + } \ + static nsISupports* Upcast(_class* p) { \ + return NS_ISUPPORTS_CAST(_base*, p); \ + } \ + template <typename T> \ + friend nsISupports* ToSupports(T* p, NS_CYCLE_COLLECTION_INNERCLASS* dummy); \ + NS_IMETHOD_(void) Unlink(void* p) override; + +#define NS_PARTICIPANT_AS(type, participant) \ + const_cast<type*>(reinterpret_cast<const type*>(participant)) + +#define NS_IMPL_GET_XPCOM_CYCLE_COLLECTION_PARTICIPANT(_class) \ + static constexpr nsXPCOMCycleCollectionParticipant* GetParticipant() { \ + return &_class::NS_CYCLE_COLLECTION_INNERNAME; \ + } + +/** + * We use this macro to force that classes that inherit from a ccable class and + * declare their own participant declare themselves as inherited cc classes. + * To avoid possibly unnecessary vtables we only do this checking in debug + * builds. + */ +#ifdef DEBUG +# define NOT_INHERITED_CANT_OVERRIDE \ + virtual void BaseCycleCollectable() final {} +#else +# define NOT_INHERITED_CANT_OVERRIDE +#endif + +#define NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(_class, _base) \ + class NS_CYCLE_COLLECTION_INNERCLASS \ + : public nsXPCOMCycleCollectionParticipant { \ + public: \ + constexpr explicit NS_CYCLE_COLLECTION_INNERCLASS(Flags aFlags = 0) \ + : nsXPCOMCycleCollectionParticipant(aFlags) {} \ + \ + private: \ + NS_DECL_CYCLE_COLLECTION_CLASS_BODY(_class, _base) \ + NS_IMPL_GET_XPCOM_CYCLE_COLLECTION_PARTICIPANT(_class) \ + }; \ + NS_CHECK_FOR_RIGHT_PARTICIPANT_IMPL(_class) \ + static NS_CYCLE_COLLECTION_INNERCLASS NS_CYCLE_COLLECTION_INNERNAME; \ + NOT_INHERITED_CANT_OVERRIDE + +#define NS_DECL_CYCLE_COLLECTION_CLASS(_class) \ + NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(_class, _class) + +// Cycle collector helper for ambiguous classes that can sometimes be skipped. +#define NS_DECL_CYCLE_COLLECTION_SKIPPABLE_CLASS_AMBIGUOUS(_class, _base) \ + class NS_CYCLE_COLLECTION_INNERCLASS \ + : public nsXPCOMCycleCollectionParticipant { \ + public: \ + constexpr explicit NS_CYCLE_COLLECTION_INNERCLASS( \ + Flags aFlags = FlagMightSkip) /* We always want skippability. */ \ + : nsXPCOMCycleCollectionParticipant(aFlags | FlagMightSkip) {} \ + \ + private: \ + NS_DECL_CYCLE_COLLECTION_CLASS_BODY(_class, _base) \ + NS_IMETHOD_(bool) CanSkipReal(void* p, bool aRemovingAllowed) override; \ + NS_IMETHOD_(bool) CanSkipInCCReal(void* p) override; \ + NS_IMETHOD_(bool) CanSkipThisReal(void* p) override; \ + NS_IMPL_GET_XPCOM_CYCLE_COLLECTION_PARTICIPANT(_class) \ + }; \ + NS_CHECK_FOR_RIGHT_PARTICIPANT_IMPL(_class) \ + static NS_CYCLE_COLLECTION_INNERCLASS NS_CYCLE_COLLECTION_INNERNAME; \ + NOT_INHERITED_CANT_OVERRIDE + +#define NS_DECL_CYCLE_COLLECTION_SKIPPABLE_CLASS(_class) \ + NS_DECL_CYCLE_COLLECTION_SKIPPABLE_CLASS_AMBIGUOUS(_class, _class) + +#define NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(_class, _base) \ + class NS_CYCLE_COLLECTION_INNERCLASS \ + : public nsXPCOMCycleCollectionParticipant { \ + public: \ + constexpr explicit NS_CYCLE_COLLECTION_INNERCLASS(Flags aFlags = 0) \ + : nsXPCOMCycleCollectionParticipant(aFlags) {} \ + \ + private: \ + NS_DECL_CYCLE_COLLECTION_CLASS_BODY(_class, _base) \ + NS_IMETHOD_(void) \ + Trace(void* p, const TraceCallbacks& cb, void* closure) override; \ + NS_IMPL_GET_XPCOM_CYCLE_COLLECTION_PARTICIPANT(_class) \ + }; \ + NS_CHECK_FOR_RIGHT_PARTICIPANT_IMPL(_class) \ + static NS_CYCLE_COLLECTION_INNERCLASS NS_CYCLE_COLLECTION_INNERNAME; \ + NOT_INHERITED_CANT_OVERRIDE + +#define NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS_AMBIGUOUS( \ + _class, _base) \ + class NS_CYCLE_COLLECTION_INNERCLASS \ + : public nsXPCOMCycleCollectionParticipant { \ + public: \ + constexpr explicit NS_CYCLE_COLLECTION_INNERCLASS( \ + Flags aFlags = FlagMightSkip) /* We always want skippability. */ \ + : nsXPCOMCycleCollectionParticipant(aFlags | FlagMightSkip) {} \ + \ + private: \ + NS_DECL_CYCLE_COLLECTION_CLASS_BODY(_class, _base) \ + NS_IMETHOD_(void) \ + Trace(void* p, const TraceCallbacks& cb, void* closure) override; \ + NS_IMETHOD_(bool) CanSkipReal(void* p, bool aRemovingAllowed) override; \ + NS_IMETHOD_(bool) CanSkipInCCReal(void* p) override; \ + NS_IMETHOD_(bool) CanSkipThisReal(void* p) override; \ + NS_IMPL_GET_XPCOM_CYCLE_COLLECTION_PARTICIPANT(_class) \ + }; \ + NS_CHECK_FOR_RIGHT_PARTICIPANT_IMPL(_class) \ + static NS_CYCLE_COLLECTION_INNERCLASS NS_CYCLE_COLLECTION_INNERNAME; \ + NOT_INHERITED_CANT_OVERRIDE + +#define NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS(_class) \ + NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS_AMBIGUOUS(_class, \ + _class) + +#define NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS_INHERITED( \ + _class, _base_class) \ + class NS_CYCLE_COLLECTION_INNERCLASS \ + : public NS_CYCLE_COLLECTION_CLASSNAME(_base_class) { \ + public: \ + constexpr explicit NS_CYCLE_COLLECTION_INNERCLASS( \ + Flags aFlags = FlagMightSkip | /* We always want skippability. */ \ + FlagMultiZoneJSHolder) \ + : NS_CYCLE_COLLECTION_CLASSNAME(_base_class)(aFlags | FlagMightSkip) { \ + } \ + \ + private: \ + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED_BODY(_class, _base_class) \ + NS_IMETHOD_(void) \ + Trace(void* p, const TraceCallbacks& cb, void* closure) override; \ + NS_IMETHOD_(bool) CanSkipReal(void* p, bool aRemovingAllowed) override; \ + NS_IMETHOD_(bool) CanSkipInCCReal(void* p) override; \ + NS_IMETHOD_(bool) CanSkipThisReal(void* p) override; \ + NS_IMPL_GET_XPCOM_CYCLE_COLLECTION_PARTICIPANT(_class) \ + }; \ + NS_CHECK_FOR_RIGHT_PARTICIPANT_IMPL_INHERITED(_class) \ + static NS_CYCLE_COLLECTION_INNERCLASS NS_CYCLE_COLLECTION_INNERNAME; + +#define NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(_class) \ + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(_class, _class) + +#define NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED_BODY(_class, _base_class) \ + public: \ + NS_IMETHOD TraverseNative(void* p, nsCycleCollectionTraversalCallback& cb) \ + override; \ + NS_DECL_CYCLE_COLLECTION_CLASS_NAME_METHOD(_class) \ + static _class* Downcast(nsISupports* s) { \ + return static_cast<_class*>(static_cast<_base_class*>( \ + NS_CYCLE_COLLECTION_CLASSNAME(_base_class)::Downcast(s))); \ + } \ + NS_IMETHOD_(void) Unlink(void* p) override; + +#define NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(_class, _base_class) \ + class NS_CYCLE_COLLECTION_INNERCLASS \ + : public NS_CYCLE_COLLECTION_CLASSNAME(_base_class) { \ + public: \ + constexpr explicit NS_CYCLE_COLLECTION_INNERCLASS(Flags aFlags = 0) \ + : NS_CYCLE_COLLECTION_CLASSNAME(_base_class)(aFlags) {} \ + \ + private: \ + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED_BODY(_class, _base_class) \ + NS_IMPL_GET_XPCOM_CYCLE_COLLECTION_PARTICIPANT(_class) \ + }; \ + NS_CHECK_FOR_RIGHT_PARTICIPANT_IMPL_INHERITED(_class) \ + static NS_CYCLE_COLLECTION_INNERCLASS NS_CYCLE_COLLECTION_INNERNAME; + +#define NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(_class, \ + _base_class) \ + class NS_CYCLE_COLLECTION_INNERCLASS \ + : public NS_CYCLE_COLLECTION_CLASSNAME(_base_class) { \ + public: \ + constexpr explicit NS_CYCLE_COLLECTION_INNERCLASS(Flags aFlags = 0) \ + : NS_CYCLE_COLLECTION_CLASSNAME(_base_class)(aFlags | \ + FlagMultiZoneJSHolder) {} \ + \ + private: \ + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED_BODY(_class, _base_class) \ + NS_IMETHOD_(void) \ + Trace(void* p, const TraceCallbacks& cb, void* closure) override; \ + NS_IMPL_GET_XPCOM_CYCLE_COLLECTION_PARTICIPANT(_class) \ + }; \ + NS_CHECK_FOR_RIGHT_PARTICIPANT_IMPL_INHERITED(_class) \ + static NS_CYCLE_COLLECTION_INNERCLASS NS_CYCLE_COLLECTION_INNERNAME; + +// Cycle collector participant declarations. + +#define NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS_BODY(_class) \ + public: \ + NS_IMETHOD_(void) Root(void* p) override { \ + static_cast<_class*>(p)->AddRef(); \ + } \ + NS_IMETHOD_(void) Unlink(void* n) override; \ + NS_IMETHOD_(void) Unroot(void* p) override { \ + static_cast<_class*>(p)->Release(); \ + } \ + NS_IMETHOD TraverseNative(void* n, nsCycleCollectionTraversalCallback& cb) \ + override; \ + NS_DECL_CYCLE_COLLECTION_CLASS_NAME_METHOD(_class) \ + NS_IMETHOD_(void) DeleteCycleCollectable(void* n) override { \ + DowncastCCParticipant<_class>(n)->DeleteCycleCollectable(); \ + } \ + static _class* Downcast(void* s) { \ + return DowncastCCParticipant<_class>(s); \ + } \ + static void* Upcast(_class* p) { return static_cast<void*>(p); } + +#define NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(_class) \ + void DeleteCycleCollectable(void) { delete this; } \ + class NS_CYCLE_COLLECTION_INNERCLASS : public nsCycleCollectionParticipant { \ + public: \ + constexpr explicit NS_CYCLE_COLLECTION_INNERCLASS(Flags aFlags = 0) \ + : nsCycleCollectionParticipant(aFlags) {} \ + \ + private: \ + NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS_BODY(_class) \ + static constexpr nsCycleCollectionParticipant* GetParticipant() { \ + return &_class::NS_CYCLE_COLLECTION_INNERNAME; \ + } \ + }; \ + static NS_CYCLE_COLLECTION_INNERCLASS NS_CYCLE_COLLECTION_INNERNAME; + +#define NS_DECL_CYCLE_COLLECTION_SKIPPABLE_NATIVE_CLASS(_class) \ + void DeleteCycleCollectable(void) { delete this; } \ + class NS_CYCLE_COLLECTION_INNERCLASS : public nsCycleCollectionParticipant { \ + public: \ + constexpr explicit NS_CYCLE_COLLECTION_INNERCLASS( \ + Flags aFlags = FlagMightSkip) /* We always want skippability. */ \ + : nsCycleCollectionParticipant(aFlags | FlagMightSkip) {} \ + \ + private: \ + NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS_BODY(_class) \ + NS_IMETHOD_(bool) CanSkipReal(void* p, bool aRemovingAllowed) override; \ + NS_IMETHOD_(bool) CanSkipInCCReal(void* p) override; \ + NS_IMETHOD_(bool) CanSkipThisReal(void* p) override; \ + static nsCycleCollectionParticipant* GetParticipant() { \ + return &_class::NS_CYCLE_COLLECTION_INNERNAME; \ + } \ + }; \ + static NS_CYCLE_COLLECTION_INNERCLASS NS_CYCLE_COLLECTION_INNERNAME; + +#define NS_DECL_CYCLE_COLLECTION_SKIPPABLE_NATIVE_CLASS_WITH_CUSTOM_DELETE( \ + _class) \ + class NS_CYCLE_COLLECTION_INNERCLASS : public nsCycleCollectionParticipant { \ + public: \ + constexpr NS_CYCLE_COLLECTION_INNERCLASS() \ + : nsCycleCollectionParticipant(true) {} \ + \ + private: \ + NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS_BODY(_class) \ + NS_IMETHOD_(bool) CanSkipReal(void* p, bool aRemovingAllowed) override; \ + NS_IMETHOD_(bool) CanSkipInCCReal(void* p) override; \ + NS_IMETHOD_(bool) CanSkipThisReal(void* p) override; \ + static nsCycleCollectionParticipant* GetParticipant() { \ + return &_class::NS_CYCLE_COLLECTION_INNERNAME; \ + } \ + }; \ + static NS_CYCLE_COLLECTION_INNERCLASS NS_CYCLE_COLLECTION_INNERNAME; + +#define NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(_class) \ + void DeleteCycleCollectable(void) { delete this; } \ + class NS_CYCLE_COLLECTION_INNERCLASS : public nsScriptObjectTracer { \ + public: \ + constexpr explicit NS_CYCLE_COLLECTION_INNERCLASS(Flags aFlags = 0) \ + : nsScriptObjectTracer(aFlags) {} \ + \ + private: \ + NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS_BODY(_class) \ + NS_IMETHOD_(void) \ + Trace(void* p, const TraceCallbacks& cb, void* closure) override; \ + static constexpr nsScriptObjectTracer* GetParticipant() { \ + return &_class::NS_CYCLE_COLLECTION_INNERNAME; \ + } \ + }; \ + static NS_CYCLE_COLLECTION_INNERCLASS NS_CYCLE_COLLECTION_INNERNAME; + +#define NS_IMPL_CYCLE_COLLECTION_CLASS(_class) \ + _class::NS_CYCLE_COLLECTION_INNERCLASS _class::NS_CYCLE_COLLECTION_INNERNAME; + +// By default JS holders are treated as if they may store pointers to multiple +// zones, but one may optimize GC handling by using single zone holders. +#define NS_IMPL_CYCLE_COLLECTION_SINGLE_ZONE_SCRIPT_HOLDER_CLASS(_class) \ + _class::NS_CYCLE_COLLECTION_INNERCLASS \ + _class::NS_CYCLE_COLLECTION_INNERNAME( \ + nsCycleCollectionParticipant::FlagMaybeSingleZoneJSHolder); + +// NB: This is not something you usually want to use. It is here to allow +// adding things to the CC graph to help debugging via CC logs, but it does not +// traverse or unlink anything, so it is useless for anything else. +#define NS_IMPL_CYCLE_COLLECTION_0(_class) \ + NS_IMPL_CYCLE_COLLECTION_CLASS(_class) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(_class) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK_END \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(_class) \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +#define NS_IMPL_CYCLE_COLLECTION(_class, ...) \ + NS_IMPL_CYCLE_COLLECTION_CLASS(_class) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(_class) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK(__VA_ARGS__) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK_END \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(_class) \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(__VA_ARGS__) \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +// If you are looking for NS_IMPL_CYCLE_COLLECTION_INHERITED_0(_class, _base) +// you should instead not declare any cycle collected stuff in _class, so it +// will just inherit the CC declarations from _base. + +#define NS_IMPL_CYCLE_COLLECTION_INHERITED(_class, _base, ...) \ + NS_IMPL_CYCLE_COLLECTION_CLASS(_class) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(_class, _base) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK(__VA_ARGS__) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK_END \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(_class, _base) \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(__VA_ARGS__) \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +#define NS_CYCLE_COLLECTION_NOTE_EDGE_NAME CycleCollectionNoteEdgeName + +/** + * Convenience macros for defining nISupports methods in a cycle collected + * class. + */ + +#define NS_IMPL_QUERY_INTERFACE_CYCLE_COLLECTION_INHERITED(aClass, aSuper, \ + ...) \ + NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(aClass) \ + NS_INTERFACE_TABLE_INHERITED(aClass, __VA_ARGS__) \ + NS_INTERFACE_TABLE_TAIL_INHERITING(aSuper) + +#define NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(aClass, aSuper, ...) \ + NS_IMPL_QUERY_INTERFACE_CYCLE_COLLECTION_INHERITED(aClass, aSuper, \ + __VA_ARGS__) \ + NS_IMPL_ADDREF_INHERITED(aClass, aSuper) \ + NS_IMPL_RELEASE_INHERITED(aClass, aSuper) + +#define NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(aClass, aSuper) \ + NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(aClass) \ + NS_INTERFACE_MAP_END_INHERITING(aSuper) \ + NS_IMPL_ADDREF_INHERITED(aClass, aSuper) \ + NS_IMPL_RELEASE_INHERITED(aClass, aSuper) + +/** + * Equivalency of the high three words where two IIDs have the same + * top three words but not the same low word. + */ +inline bool TopThreeWordsEquals(const nsID& aID, const nsID& aOther1, + const nsID& aOther2) { + MOZ_ASSERT((((uint32_t*)&aOther1.m0)[0] == ((uint32_t*)&aOther2.m0)[0]) && + (((uint32_t*)&aOther1.m0)[1] == ((uint32_t*)&aOther2.m0)[1]) && + (((uint32_t*)&aOther1.m0)[2] == ((uint32_t*)&aOther2.m0)[2]) && + (((uint32_t*)&aOther1.m0)[3] != ((uint32_t*)&aOther2.m0)[3])); + + return ((((uint32_t*)&aID.m0)[0] == ((uint32_t*)&aOther1.m0)[0]) && + (((uint32_t*)&aID.m0)[1] == ((uint32_t*)&aOther1.m0)[1]) && + (((uint32_t*)&aID.m0)[2] == ((uint32_t*)&aOther1.m0)[2])); +} + +/** + * Equivalency of the fourth word where the two IIDs have the same + * top three words but not the same low word. + */ +inline bool LowWordEquals(const nsID& aID, const nsID& aOther) { + return (((uint32_t*)&aID.m0)[3] == ((uint32_t*)&aOther.m0)[3]); +} + +// Template magic to modify JS::Heap without including relevant headers, to +// prevent excessive header dependency. +template <typename T> +inline void ImplCycleCollectionUnlink(JS::Heap<T>& aField) { + aField.setNull(); +} +template <typename T> +inline void ImplCycleCollectionUnlink(JS::Heap<T*>& aField) { + aField = nullptr; +} + +#define NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBERS(...) \ + MOZ_ASSERT(!IsSingleZoneJSHolder()); \ + MOZ_FOR_EACH(NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK, (), \ + (__VA_ARGS__)) + +#define NS_IMPL_CYCLE_COLLECTION_WITH_JS_MEMBERS(class_, native_members_, \ + js_members_) \ + NS_IMPL_CYCLE_COLLECTION_CLASS(class_) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(class_) \ + using ::ImplCycleCollectionUnlink; \ + NS_IMPL_CYCLE_COLLECTION_UNLINK( \ + MOZ_FOR_EACH_EXPAND_HELPER native_members_) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK(MOZ_FOR_EACH_EXPAND_HELPER js_members_) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK_END \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(class_) \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE( \ + MOZ_FOR_EACH_EXPAND_HELPER native_members_) \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END \ + NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(class_) \ + NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBERS( \ + MOZ_FOR_EACH_EXPAND_HELPER js_members_) \ + NS_IMPL_CYCLE_COLLECTION_TRACE_END + +#define NS_IMPL_CYCLE_COLLECTION_INHERITED_WITH_JS_MEMBERS( \ + class_, _base, native_members_, js_members_) \ + NS_IMPL_CYCLE_COLLECTION_CLASS(class_) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(class_, _base) \ + using ::ImplCycleCollectionUnlink; \ + NS_IMPL_CYCLE_COLLECTION_UNLINK( \ + MOZ_FOR_EACH_EXPAND_HELPER native_members_) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK(MOZ_FOR_EACH_EXPAND_HELPER js_members_) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK_END \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(class_, _base) \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE( \ + MOZ_FOR_EACH_EXPAND_HELPER native_members_) \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END \ + NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(class_, _base) \ + NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBERS( \ + MOZ_FOR_EACH_EXPAND_HELPER js_members_) \ + NS_IMPL_CYCLE_COLLECTION_TRACE_END + +template <typename... Elements> +inline void ImplCycleCollectionUnlink(std::tuple<Elements...>& aField) { + std::apply([](auto&&... aArgs) { (ImplCycleCollectionUnlink(aArgs), ...); }, + aField); +} +template <typename... Elements> +inline void ImplCycleCollectionTraverse( + nsCycleCollectionTraversalCallback& aCallback, + std::tuple<Elements...>& aField, const char* aName, uint32_t aFlags) { + aFlags |= CycleCollectionEdgeNameArrayFlag; + std::apply( + [&](auto&&... aArgs) { + (ImplCycleCollectionTraverse(aCallback, aArgs, aName, aFlags), ...); + }, + aField); +} + +#endif // nsCycleCollectionParticipant_h__ diff --git a/xpcom/base/nsCycleCollectionTraversalCallback.h b/xpcom/base/nsCycleCollectionTraversalCallback.h new file mode 100644 index 0000000000..9317a5e23a --- /dev/null +++ b/xpcom/base/nsCycleCollectionTraversalCallback.h @@ -0,0 +1,70 @@ +/* -*- 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 nsCycleCollectionTraversalCallback_h__ +#define nsCycleCollectionTraversalCallback_h__ + +#include <cstdint> +#include "nscore.h" + +class nsCycleCollectionParticipant; +class nsISupports; +class JSObject; + +namespace JS { +class GCCellPtr; +} + +class NS_NO_VTABLE nsCycleCollectionTraversalCallback { + public: + // You must call DescribeRefCountedNode() with an accurate + // refcount, otherwise cycle collection will fail, and probably crash. + // If the callback cares about objname, it should put + // WANT_DEBUG_INFO in mFlags. + NS_IMETHOD_(void) + DescribeRefCountedNode(nsrefcnt aRefcount, const char* aObjName) = 0; + // Note, aCompartmentAddress is 0 if it is unknown. + NS_IMETHOD_(void) + DescribeGCedNode(bool aIsMarked, const char* aObjName, + uint64_t aCompartmentAddress = 0) = 0; + + NS_IMETHOD_(void) NoteXPCOMChild(nsISupports* aChild) = 0; + NS_IMETHOD_(void) NoteJSChild(JS::GCCellPtr aThing) = 0; + NS_IMETHOD_(void) + NoteNativeChild(void* aChild, nsCycleCollectionParticipant* aHelper) = 0; + + NS_IMETHOD_(void) + NoteWeakMapping(JSObject* aKey, nsISupports* aVal, + nsCycleCollectionParticipant* aValParticipant) = 0; + + // Give a name to the edge associated with the next call to + // NoteXPCOMChild, NoteJSObject, NoteJSScript, or NoteNativeChild. + // Callbacks who care about this should set WANT_DEBUG_INFO in the + // flags. + NS_IMETHOD_(void) NoteNextEdgeName(const char* aName) = 0; + + enum { + // Values for flags: + + // Caller should call NoteNextEdgeName and pass useful objName + // to DescribeRefCountedNode and DescribeGCedNode. + WANT_DEBUG_INFO = (1 << 0), + + // Caller should not skip objects that we know will be + // uncollectable. + WANT_ALL_TRACES = (1 << 1) + }; + uint32_t Flags() const { return mFlags; } + bool WantDebugInfo() const { return (mFlags & WANT_DEBUG_INFO) != 0; } + bool WantAllTraces() const { return (mFlags & WANT_ALL_TRACES) != 0; } + + protected: + nsCycleCollectionTraversalCallback() : mFlags(0) {} + + uint32_t mFlags; +}; + +#endif // nsCycleCollectionTraversalCallback_h__ diff --git a/xpcom/base/nsCycleCollector.cpp b/xpcom/base/nsCycleCollector.cpp new file mode 100644 index 0000000000..9c885b6ef1 --- /dev/null +++ b/xpcom/base/nsCycleCollector.cpp @@ -0,0 +1,4042 @@ +/* -*- 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. A snow-white node +// 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/CycleCollectedJSRuntime.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/HashFunctions.h" +#include "mozilla/HashTable.h" +#include "mozilla/HoldDropJSObjects.h" +/* This must occur *after* base/process_util.h to avoid typedefs conflicts. */ +#include <stdint.h> +#include <stdio.h> + +#include <utility> + +#include "js/SliceBudget.h" +#include "mozilla/Attributes.h" +#include "mozilla/Likely.h" +#include "mozilla/LinkedList.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/MruCache.h" +#include "mozilla/PoisonIOInterposer.h" +#include "mozilla/ProfilerLabels.h" +#include "mozilla/SegmentedVector.h" +#include "mozilla/Telemetry.h" +#include "mozilla/ThreadLocal.h" +#include "mozilla/UniquePtr.h" +#include "nsCycleCollectionNoteRootCallback.h" +#include "nsCycleCollectionParticipant.h" +#include "nsCycleCollector.h" +#include "nsDeque.h" +#include "nsDumpUtils.h" +#include "nsExceptionHandler.h" +#include "nsIConsoleService.h" +#include "nsICycleCollectorListener.h" +#include "nsIFile.h" +#include "nsIMemoryReporter.h" +#include "nsISerialEventTarget.h" +#include "nsPrintfCString.h" +#include "nsTArray.h" +#include "nsThreadUtils.h" +#include "nsXULAppAPI.h" +#include "prenv.h" +#include "xpcpublic.h" + +using namespace mozilla; + +struct NurseryPurpleBufferEntry { + void* mPtr; + nsCycleCollectionParticipant* mParticipant; + nsCycleCollectingAutoRefCnt* mRefCnt; +}; + +#define NURSERY_PURPLE_BUFFER_SIZE 2048 +bool gNurseryPurpleBufferEnabled = true; +NurseryPurpleBufferEntry gNurseryPurpleBufferEntry[NURSERY_PURPLE_BUFFER_SIZE]; +uint32_t gNurseryPurpleBufferEntryCount = 0; + +void ClearNurseryPurpleBuffer(); + +static void SuspectUsingNurseryPurpleBuffer( + void* aPtr, nsCycleCollectionParticipant* aCp, + nsCycleCollectingAutoRefCnt* aRefCnt) { + MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); + MOZ_ASSERT(gNurseryPurpleBufferEnabled); + if (gNurseryPurpleBufferEntryCount == NURSERY_PURPLE_BUFFER_SIZE) { + ClearNurseryPurpleBuffer(); + } + + gNurseryPurpleBufferEntry[gNurseryPurpleBufferEntryCount] = {aPtr, aCp, + aRefCnt}; + ++gNurseryPurpleBufferEntryCount; +} + +// #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_SHUTDOWN_SKIP: If set to a non-negative integer value n, then +// skip logging for the first n shutdown CCs. This implies MOZ_CC_LOG_SHUTDOWN. +// The first log or two are much larger than the rest, so it can be useful to +// reduce the total size of logs if you know already that the initial logs +// aren't interesting. +// +// 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 "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; + int32_t mLogShutdownSkip = 0; + + nsCycleCollectorParams() + : mLogAll(PR_GetEnv("MOZ_CC_LOG_ALL") != nullptr), + mLogShutdown(PR_GetEnv("MOZ_CC_LOG_SHUTDOWN") != nullptr), + mAllTracesAll(false), + mAllTracesShutdown(false) { + if (const char* lssEnv = PR_GetEnv("MOZ_CC_LOG_SHUTDOWN_SKIP")) { + mLogShutdown = true; + nsDependentCString lssString(lssEnv); + nsresult rv; + int32_t lss = lssString.ToInteger(&rv); + if (NS_SUCCEEDED(rv) && lss >= 0) { + mLogShutdownSkip = lss; + } + } + + 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_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; + } + } + } + + // aShutdownCount is how many shutdown CCs we've started. + // For non-shutdown CCs, we'll pass in 0. + // For the first shutdown CC, we'll pass in 1. + bool LogThisCC(int32_t aShutdownCount) { + if (mLogAll) { + return mLogThisThread; + } + if (aShutdownCount == 0 || !mLogShutdown) { + return false; + } + if (aShutdownCount <= mLogShutdownSkip) { + return false; + } + return 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() = default; + void Checkpoint(const char* aEvent) {} +}; +#endif + +//////////////////////////////////////////////////////////////////////// +// Base types +//////////////////////////////////////////////////////////////////////// + +class 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) = default; + + 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) \ + do { \ + if (NS_IsMainThread()) { \ + Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR##_name, _value); \ + } else { \ + Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_WORKER##_name, _value); \ + } \ + } while (0) + +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. +class PtrInfo final { + public: + // mParticipant knows a more concrete type. + 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) { + 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() + : mPointer{nullptr}, + mParticipant{nullptr}, + mColor{0}, + mInternalRefs{0}, + mRefCount{0} { + MOZ_ASSERT_UNREACHABLE("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; + } + + void AnnotatedReleaseAssert(bool aCondition, const char* aMessage); +}; + +void PtrInfo::AnnotatedReleaseAssert(bool aCondition, const char* aMessage) { + if (aCondition) { + return; + } + + const char* piName = "Unknown"; + if (mParticipant) { + piName = mParticipant->ClassName(); + } + nsPrintfCString msg("%s, for class %s", aMessage, piName); + NS_WARNING(msg.get()); + CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::CycleCollector, + msg); + + MOZ_CRASH(); +} + +/** + * 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() : mNext{nullptr} { + MOZ_ASSERT_UNREACHABLE("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() { MOZ_ASSERT_UNREACHABLE("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; +}; + +struct PtrToNodeHashPolicy { + using Key = PtrInfo*; + using Lookup = void*; + + static js::HashNumber hash(const Lookup& aLookup) { + return mozilla::HashGeneric(aLookup); + } + + static bool match(const Key& aKey, const Lookup& aLookup) { + return aKey->mPointer == aLookup; + } +}; + +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: + friend CCGraphBuilder; + + mozilla::HashSet<PtrInfo*, PtrToNodeHashPolicy> mPtrInfoMap; + + bool mOutOfMemory; + + static const uint32_t kInitialMapLength = 16384; + + public: + CCGraph() + : mRootCount(0), mPtrInfoMap(kInitialMapLength), mOutOfMemory(false) {} + + ~CCGraph() = default; + + void Init() { MOZ_ASSERT(IsEmpty(), "Failed to call CCGraph::Clear"); } + + void Clear() { + mNodes.Clear(); + mEdges.Clear(); + mWeakMaps.Clear(); + mRootCount = 0; + mPtrInfoMap.clearAndCompact(); + mOutOfMemory = false; + } + +#ifdef DEBUG + bool IsEmpty() { + return mNodes.IsEmpty() && mEdges.IsEmpty() && mWeakMaps.IsEmpty() && + mRootCount == 0 && mPtrInfoMap.empty(); + } +#endif + + PtrInfo* FindNode(void* aPtr); + void RemoveObjectFromMap(void* aObject); + + uint32_t MapCount() const { return mPtrInfoMap.count(); } + + 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 += mPtrInfoMap.shallowSizeOfExcludingThis(aMallocSizeOf); + + return n; + } +}; + +PtrInfo* CCGraph::FindNode(void* aPtr) { + auto p = mPtrInfoMap.lookup(aPtr); + return p ? *p : nullptr; +} + +void CCGraph::RemoveObjectFromMap(void* aObj) { + auto p = mPtrInfoMap.lookup(aObj); + if (p) { + PtrInfo* pinfo = *p; + pinfo->mPointer = nullptr; + pinfo->mParticipant = nullptr; + mPtrInfoMap.remove(p); + } +} + +static nsISupports* CanonicalizeXPCOMParticipant(nsISupports* aIn) { + nsISupports* out = nullptr; + aIn->QueryInterface(NS_GET_IID(nsCycleCollectionISupports), + reinterpret_cast<void**>(&out)); + return out; +} + +struct nsPurpleBufferEntry { + nsPurpleBufferEntry(void* aObject, nsCycleCollectingAutoRefCnt* aRefCnt, + nsCycleCollectionParticipant* aParticipant) + : mObject(aObject), mRefCnt(aRefCnt), mParticipant(aParticipant) {} + + nsPurpleBufferEntry(nsPurpleBufferEntry&& aOther) + : mObject(nullptr), mRefCnt(nullptr), mParticipant(nullptr) { + Swap(aOther); + } + + void Swap(nsPurpleBufferEntry& aOther) { + std::swap(mObject, aOther.mObject); + std::swap(mRefCnt, aOther.mRefCnt); + std::swap(mParticipant, aOther.mParticipant); + } + + void Clear() { + mRefCnt->RemoveFromPurpleBuffer(); + mRefCnt = nullptr; + mObject = nullptr; + mParticipant = nullptr; + } + + ~nsPurpleBufferEntry() { + if (mRefCnt) { + mRefCnt->RemoveFromPurpleBuffer(); + } + } + + void* mObject; + nsCycleCollectingAutoRefCnt* mRefCnt; + nsCycleCollectionParticipant* mParticipant; // nullptr for nsISupports +}; + +class nsCycleCollector; + +struct nsPurpleBuffer { + private: + uint32_t mCount; + + // Try to match the size of a jemalloc bucket, to minimize slop bytes. + // - On 32-bit platforms sizeof(nsPurpleBufferEntry) is 12, so mEntries' + // Segment is 16,372 bytes. + // - On 64-bit platforms sizeof(nsPurpleBufferEntry) is 24, so mEntries' + // Segment is 32,760 bytes. + static const uint32_t kEntriesPerSegment = 1365; + static const size_t kSegmentSize = + sizeof(nsPurpleBufferEntry) * kEntriesPerSegment; + typedef SegmentedVector<nsPurpleBufferEntry, kSegmentSize, + InfallibleAllocPolicy> + PurpleBufferVector; + PurpleBufferVector mEntries; + + public: + nsPurpleBuffer() : mCount(0) { + static_assert( + sizeof(PurpleBufferVector::Segment) == 16372 || // 32-bit + sizeof(PurpleBufferVector::Segment) == 32760 || // 64-bit + sizeof(PurpleBufferVector::Segment) == 32744, // 64-bit Windows + "ill-sized nsPurpleBuffer::mEntries"); + } + + ~nsPurpleBuffer() = default; + + // This method compacts mEntries. + template <class PurpleVisitor> + void VisitEntries(PurpleVisitor& aVisitor) { + Maybe<AutoRestore<bool>> ar; + if (NS_IsMainThread()) { + ar.emplace(gNurseryPurpleBufferEnabled); + gNurseryPurpleBufferEnabled = false; + ClearNurseryPurpleBuffer(); + } + + if (mEntries.IsEmpty()) { + return; + } + + uint32_t oldLength = mEntries.Length(); + uint32_t keptLength = 0; + auto revIter = mEntries.IterFromLast(); + auto iter = mEntries.Iter(); + // After iteration this points to the first empty entry. + auto firstEmptyIter = mEntries.Iter(); + auto iterFromLastEntry = mEntries.IterFromLast(); + for (; !iter.Done(); iter.Next()) { + nsPurpleBufferEntry& e = iter.Get(); + if (e.mObject) { + if (!aVisitor.Visit(*this, &e)) { + return; + } + } + + // Visit call above may have cleared the entry, or the entry was empty + // already. + if (!e.mObject) { + // Try to find a non-empty entry from the end of the vector. + for (; !revIter.Done(); revIter.Prev()) { + nsPurpleBufferEntry& otherEntry = revIter.Get(); + if (&e == &otherEntry) { + break; + } + if (otherEntry.mObject) { + if (!aVisitor.Visit(*this, &otherEntry)) { + return; + } + // Visit may have cleared otherEntry. + if (otherEntry.mObject) { + e.Swap(otherEntry); + revIter.Prev(); // We've swapped this now empty entry. + break; + } + } + } + } + + // Entry is non-empty even after the Visit call, ensure it is kept + // in mEntries. + if (e.mObject) { + firstEmptyIter.Next(); + ++keptLength; + } + + if (&e == &revIter.Get()) { + break; + } + } + + // There were some empty entries. + if (oldLength != keptLength) { + // While visiting entries, some new ones were possibly added. This can + // happen during CanSkip. Move all such new entries to be after other + // entries. Note, we don't call Visit on newly added entries! + if (&iterFromLastEntry.Get() != &mEntries.GetLast()) { + iterFromLastEntry.Next(); // Now pointing to the first added entry. + auto& iterForNewEntries = iterFromLastEntry; + while (!iterForNewEntries.Done()) { + MOZ_ASSERT(!firstEmptyIter.Done()); + MOZ_ASSERT(!firstEmptyIter.Get().mObject); + firstEmptyIter.Get().Swap(iterForNewEntries.Get()); + firstEmptyIter.Next(); + iterForNewEntries.Next(); + } + } + + mEntries.PopLastN(oldLength - keptLength); + } + } + + void FreeBlocks() { + mCount = 0; + mEntries.Clear(); + } + + void SelectPointers(CCGraphBuilder& aBuilder); + + // RemoveSkippable removes entries from the purple buffer synchronously + // (1) if !aAsyncSnowWhiteFreeing and nsPurpleBufferEntry::mRefCnt is 0 or + // (2) if nsXPCOMCycleCollectionParticipant::CanSkip() for the obj or + // (3) if nsPurpleBufferEntry::mRefCnt->IsPurple() is false. + // (4) If aRemoveChildlessNodes 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, js::SliceBudget& aBudget, + bool aRemoveChildlessNodes, bool aAsyncSnowWhiteFreeing, + CC_ForgetSkippableCallback aCb); + + MOZ_ALWAYS_INLINE void Put(void* aObject, nsCycleCollectionParticipant* aCp, + nsCycleCollectingAutoRefCnt* aRefCnt) { + nsPurpleBufferEntry entry(aObject, aRefCnt, aCp); + Unused << mEntries.Append(std::move(entry)); + MOZ_ASSERT(!entry.mRefCnt, "Move didn't work!"); + ++mCount; + } + + void Remove(nsPurpleBufferEntry* aEntry) { + MOZ_ASSERT(mCount != 0, "must have entries"); + --mCount; + aEntry->Clear(); + } + + uint32_t Count() const { return mCount; } + + size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { + return mEntries.SizeOfExcludingThis(aMallocSizeOf); + } +}; + +static bool AddPurpleRoot(CCGraphBuilder& aBuilder, void* aRoot, + nsCycleCollectionParticipant* aParti); + +struct SelectPointersVisitor { + explicit SelectPointersVisitor(CCGraphBuilder& aBuilder) + : mBuilder(aBuilder) {} + + bool 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); + } + return true; + } + + private: + CCGraphBuilder& mBuilder; +}; + +void nsPurpleBuffer::SelectPointers(CCGraphBuilder& aBuilder) { + SelectPointersVisitor visitor(aBuilder); + VisitEntries(visitor); + + MOZ_ASSERT(mCount == 0, "AddPurpleRoot failed"); + if (mCount == 0) { + FreeBlocks(); + } +} + +enum ccPhase { + IdlePhase, + GraphBuildingPhase, + ScanAndCollectWhitePhase, + CleanupPhase +}; + +enum ccIsManual { CCIsNotManual = false, CCIsManual = true }; + +//////////////////////////////////////////////////////////////////////// +// 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; + + CycleCollectedJSRuntime* mCCJSRuntime; + + ccPhase mIncrementalPhase; + int32_t mShutdownCount = 0; + CCGraph mGraph; + UniquePtr<CCGraphBuilder> mBuilder; + RefPtr<nsCycleCollectorLogger> mLogger; + +#ifdef DEBUG + nsISerialEventTarget* mEventTarget; +#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 SetCCJSRuntime(CycleCollectedJSRuntime* aCCRuntime); + void ClearCCJSRuntime(); + + void SetBeforeUnlinkCallback(CC_BeforeUnlinkCallback aBeforeUnlinkCB) { + CheckThreadSafety(); + mBeforeUnlinkCB = aBeforeUnlinkCB; + } + + void SetForgetSkippableCallback( + CC_ForgetSkippableCallback aForgetSkippableCB) { + CheckThreadSafety(); + mForgetSkippableCB = aForgetSkippableCB; + } + + void Suspect(void* aPtr, nsCycleCollectionParticipant* aCp, + nsCycleCollectingAutoRefCnt* aRefCnt); + void SuspectNurseryEntries(); + uint32_t SuspectedCount(); + void ForgetSkippable(js::SliceBudget& aBudget, bool aRemoveChildlessNodes, + bool aAsyncSnowWhiteFreeing); + bool FreeSnowWhite(bool aUntilNoSWInPurpleBuffer); + bool FreeSnowWhiteWithBudget(js::SliceBudget& aBudget); + + // This method assumes its argument is already canonicalized. + void RemoveObjectFromGraph(void* aPtr); + + void PrepareForGarbageCollection(); + void FinishAnyCurrentCollection(CCReason aReason); + + bool Collect(CCReason aReason, ccIsManual aIsManual, SliceBudget& aBudget, + nsICycleCollectorListener* aManualListener, + bool aPreferShorterSlices = false); + MOZ_CAN_RUN_SCRIPT + 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(); + + CycleCollectedJSRuntime* Runtime() { return mCCJSRuntime; } + + private: + void CheckThreadSafety(); + MOZ_CAN_RUN_SCRIPT + void ShutdownCollect(); + + void FixGrayBits(bool aIsShutdown, TimeLog& aTimeLog); + bool IsIncrementalGCInProgress(); + void FinishAnyIncrementalGCInProgress(); + bool ShouldMergeZones(ccIsManual aIsManual); + + void BeginCollection(CCReason aReason, ccIsManual aIsManual, + 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<PtrInfo>& aQueue); + + void CheckedPush(nsDeque<PtrInfo>& 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); +} + +static void ToParticipant(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); + MOZ_ASSERT(CanonicalizeXPCOMParticipant(nsparti) == nsparti); + nsXPCOMCycleCollectionParticipant* xcp; + ToParticipant(nsparti, &xcp); + *aCp = xcp; + } +} + +template <class Visitor> +MOZ_NEVER_INLINE void GraphWalker<Visitor>::Walk(PtrInfo* aPi) { + nsDeque<PtrInfo> queue; + CheckedPush(queue, aPi); + DoWalk(queue); +} + +template <class Visitor> +MOZ_NEVER_INLINE void GraphWalker<Visitor>::WalkFromRoots(CCGraph& aGraph) { + nsDeque<PtrInfo> 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<PtrInfo>& 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 = 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 DiscardableRunnable { + public: + explicit LogStringMessageAsync(const nsAString& aMsg) + : mozilla::DiscardableRunnable("LogStringMessageAsync"), 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, u"Garbage"_ns); + return NS_OK; + } + + NS_IMETHOD CloseCCLog() override { + if (!mCCLog.mStream) { + return NS_ERROR_UNEXPECTED; + } + CloseLog(&mCCLog, u"Cycle"_ns); + 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, "memory-reports"_ns); + 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 + u" Collector log dumped to "_ns + 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->Runtime()->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: + MOZ_ASSERT_UNREACHABLE("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) + +already_AddRefed<nsICycleCollectorListener> nsCycleCollector_createLogger() { + nsCOMPtr<nsICycleCollectorListener> logger = new nsCycleCollectorLogger(); + return logger.forget(); +} + +static bool GCThingIsGrayCCThing(JS::GCCellPtr thing) { + return JS::IsCCTraceKind(thing.kind()) && JS::GCThingIsMarkedGrayInCC(thing); +} + +static bool ValueIsGrayCCThing(const JS::Value& value) { + return JS::IsCCTraceKind(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; + UniquePtr<NodePool::Enumerator> mCurrNode; + uint32_t mNoteChildCount; + + struct PtrInfoCache : public MruCache<void*, PtrInfo*, PtrInfoCache, 491> { + static HashNumber Hash(const void* aKey) { return HashGeneric(aKey); } + static bool Match(const void* aKey, const PtrInfo* aVal) { + return aVal->mPointer == aKey; + } + }; + + PtrInfoCache mGraphCache; + + public: + CCGraphBuilder(CCGraph& aGraph, CycleCollectorResults& aResults, + CycleCollectedJSRuntime* aCCRuntime, + 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); + + void RemoveCachedEntry(void* aPtr) { mGraphCache.Remove(aPtr); } + + 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, + nsCycleCollectionParticipant* aParticipant) override; + NS_IMETHOD_(void) NoteJSRoot(JSObject* aRoot) override; + NS_IMETHOD_(void) + NoteNativeRoot(void* aRoot, + nsCycleCollectionParticipant* aParticipant) override; + NS_IMETHOD_(void) + NoteWeakMapping(JSObject* aMap, JS::GCCellPtr aKey, JSObject* aKdelegate, + JS::GCCellPtr aVal) override; + // This is used to create synthetic non-refcounted references to + // nsXPCWrappedJS from their wrapped JS objects. No map is needed, because + // the SubjectToFinalization list is like a known-black weak map, and + // no delegate is needed because the keys are all unwrapped objects. + NS_IMETHOD_(void) + NoteWeakMapping(JSObject* aKey, nsISupports* aVal, + nsCycleCollectionParticipant* aValParticipant) override; + + // nsCycleCollectionTraversalCallback methods. + NS_IMETHOD_(void) + DescribeRefCountedNode(nsrefcnt aRefCount, const char* aObjName) override; + NS_IMETHOD_(void) + DescribeGCedNode(bool aIsMarked, const char* aObjName, + uint64_t aCompartmentAddress) override; + + NS_IMETHOD_(void) NoteXPCOMChild(nsISupports* aChild) override; + NS_IMETHOD_(void) NoteJSChild(JS::GCCellPtr aThing) override; + NS_IMETHOD_(void) + NoteNativeChild(void* aChild, + nsCycleCollectionParticipant* aParticipant) override; + NS_IMETHOD_(void) NoteNextEdgeName(const char* aName) override; + + private: + 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, + CycleCollectedJSRuntime* aCCRuntime, + nsCycleCollectorLogger* aLogger, + bool aMergeZones) + : mGraph(aGraph), + mResults(aResults), + mNodeBuilder(aGraph.mNodes), + mEdgeBuilder(aGraph.mEdges), + mJSParticipant(nullptr), + mJSZoneParticipant(nullptr), + mLogger(aLogger), + mMergeZones(aMergeZones), + mNoteChildCount(0) { + // 4096 is an allocation bucket size. + static_assert(sizeof(CCGraphBuilder) <= 4096, + "Don't create too large CCGraphBuilder objects"); + + if (aCCRuntime) { + mJSParticipant = aCCRuntime->GCThingParticipant(); + mJSZoneParticipant = aCCRuntime->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() = default; + +PtrInfo* CCGraphBuilder::AddNode(void* aPtr, + nsCycleCollectionParticipant* aParticipant) { + if (mGraph.mOutOfMemory) { + return nullptr; + } + + PtrInfoCache::Entry cached = mGraphCache.Lookup(aPtr); + if (cached) { +#ifdef DEBUG + if (cached.Data()->mParticipant != aParticipant) { + auto* parti1 = cached.Data()->mParticipant; + auto* parti2 = aParticipant; + NS_WARNING( + nsPrintfCString("cached participant: %s; AddNode participant: %s\n", + parti1 ? parti1->ClassName() : "null", + parti2 ? parti2->ClassName() : "null") + .get()); + } +#endif + MOZ_ASSERT(cached.Data()->mParticipant == aParticipant, + "nsCycleCollectionParticipant shouldn't change!"); + return cached.Data(); + } + + PtrInfo* result; + auto p = mGraph.mPtrInfoMap.lookupForAdd(aPtr); + if (!p) { + // New entry + result = mNodeBuilder.Add(aPtr, aParticipant); + if (!result) { + return nullptr; + } + + if (!mGraph.mPtrInfoMap.add(p, result)) { + // `result` leaks here, but we can't free it because it's + // pool-allocated within NodePool. + mGraph.mOutOfMemory = true; + MOZ_ASSERT(false, "OOM while building cycle collector graph"); + return nullptr; + } + + } else { + result = *p; + MOZ_ASSERT(result->mParticipant == aParticipant, + "nsCycleCollectionParticipant shouldn't change!"); + } + + cached.Set(result); + + return result; +} + +bool CCGraphBuilder::AddPurpleRoot(void* aRoot, + nsCycleCollectionParticipant* aParti) { + ToParticipant(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 = MakeUnique<NodePool::Enumerator>(mGraph.mNodes); +} + +MOZ_NEVER_INLINE bool CCGraphBuilder::BuildGraph(SliceBudget& aBudget) { + MOZ_ASSERT(mCurrNode); + + while (!aBudget.isOverBudget() && !mCurrNode->IsDone()) { + mNoteChildCount = 0; + + 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->TraverseNativeAndJS(pi->mPointer, *this); + MOZ_RELEASE_ASSERT(!NS_FAILED(rv), + "Cycle collector Traverse method failed"); + } + + if (mCurrNode->AtBlockEnd()) { + SetLastChild(); + } + + aBudget.step(mNoteChildCount + 1); + } + + if (!mCurrNode->IsDone()) { + return false; + } + + if (mGraph.mRootCount > 0) { + SetLastChild(); + } + + mCurrNode = nullptr; + + return true; +} + +NS_IMETHODIMP_(void) +CCGraphBuilder::NoteXPCOMRoot(nsISupports* aRoot, + nsCycleCollectionParticipant* aParticipant) { + MOZ_ASSERT(aRoot == CanonicalizeXPCOMParticipant(aRoot)); + +#ifdef DEBUG + nsXPCOMCycleCollectionParticipant* cp; + ToParticipant(aRoot, &cp); + MOZ_ASSERT(aParticipant == cp); +#endif + + NoteRoot(aRoot, aParticipant); +} + +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) { + mCurrPi->AnnotatedReleaseAssert(aRefCount != 0, + "CCed refcounted object has zero refcount"); + mCurrPi->AnnotatedReleaseAssert( + 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; + } + + ++mNoteChildCount; + + 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; + } + + ++mNoteChildCount; + + MOZ_ASSERT(aParticipant, "Need a nsCycleCollectionParticipant!"); + if (!aParticipant->CanSkipThis(aChild) || WantAllTraces()) { + NoteChild(aChild, aParticipant, edgeName); + } +} + +NS_IMETHODIMP_(void) +CCGraphBuilder::NoteJSChild(JS::GCCellPtr aChild) { + if (!aChild) { + return; + } + + ++mNoteChildCount; + + 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); + } +} + +NS_IMETHODIMP_(void) +CCGraphBuilder::NoteWeakMapping(JSObject* aKey, nsISupports* aVal, + nsCycleCollectionParticipant* aValParticipant) { + MOZ_ASSERT(aKey, "Don't call NoteWeakMapping with a null key"); + MOZ_ASSERT(aVal, "Don't call NoteWeakMapping with a null value"); + WeakMapping* mapping = mGraph.mWeakMaps.AppendElement(); + mapping->mMap = nullptr; + mapping->mKey = AddWeakMapNode(aKey); + mapping->mKeyDelegate = mapping->mKey; + MOZ_ASSERT(js::UncheckedUnwrapWithoutExpose(aKey) == aKey); + mapping->mVal = AddNode(aVal, aValParticipant); + + if (mLogger) { + mLogger->NoteWeakMapEntry(0, (uint64_t)aKey, 0, (uint64_t)aVal); + } +} + +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) override; + NS_IMETHOD_(void) + NoteNativeChild(void* aChild, nsCycleCollectionParticipant* aHelper) override; + NS_IMETHOD_(void) NoteJSChild(JS::GCCellPtr aThing) override; + + NS_IMETHOD_(void) + NoteWeakMapping(JSObject* aKey, nsISupports* aVal, + nsCycleCollectionParticipant* aValParticipant) override {} + + NS_IMETHOD_(void) + DescribeRefCountedNode(nsrefcnt aRefcount, const char* aObjname) override {} + NS_IMETHOD_(void) + DescribeGCedNode(bool aIsMarked, const char* aObjname, + uint64_t aCompartmentAddress) override {} + NS_IMETHOD_(void) NoteNextEdgeName(const char* aName) override {} + 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(JS::GCCellPtr aChild) { + if (aChild && JS::GCThingIsMarkedGray(aChild)) { + mMayHaveChild = true; + } +} + +static bool MayHaveChild(void* aObj, nsCycleCollectionParticipant* aCp) { + ChildFinder cf; + aCp->TraverseNativeAndJS(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() { + RefPtr<JSPurpleBuffer> referenceToThis; + mReferenceToThis.swap(referenceToThis); + 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_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 + +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: + SnowWhiteKiller(nsCycleCollector* aCollector, js::SliceBudget* aBudget) + : mCollector(aCollector), + mObjects(kSegmentSize), + mBudget(aBudget), + mSawSnowWhiteObjects(false) { + MOZ_ASSERT(mCollector, "Calling SnowWhiteKiller after nsCC went away"); + } + + explicit SnowWhiteKiller(nsCycleCollector* aCollector) + : SnowWhiteKiller(aCollector, nullptr) {} + + ~SnowWhiteKiller() { + for (auto iter = mObjects.Iter(); !iter.Done(); iter.Next()) { + SnowWhiteObject& o = iter.Get(); + MaybeKillObject(o); + } + } + + private: + void MaybeKillObject(SnowWhiteObject& aObject) { + if (!aObject.mRefCnt->get() && !aObject.mRefCnt->IsInPurpleBuffer()) { + mCollector->RemoveObjectFromGraph(aObject.mPointer); + aObject.mRefCnt->stabilizeForDeletion(); + { + JS::AutoEnterCycleCollection autocc(mCollector->Runtime()->Runtime()); + aObject.mParticipant->Trace(aObject.mPointer, *this, nullptr); + } + aObject.mParticipant->DeleteCycleCollectable(aObject.mPointer); + } + } + + public: + bool Visit(nsPurpleBuffer& aBuffer, nsPurpleBufferEntry* aEntry) { + if (mBudget) { + if (mBudget->isOverBudget()) { + return false; + } + mBudget->step(); + } + + MOZ_ASSERT(aEntry->mObject, "Null object in purple buffer"); + if (!aEntry->mRefCnt->get()) { + mSawSnowWhiteObjects = true; + void* o = aEntry->mObject; + nsCycleCollectionParticipant* cp = aEntry->mParticipant; + ToParticipant(o, &cp); + SnowWhiteObject swo = {o, cp, aEntry->mRefCnt}; + if (!mBudget) { + mObjects.InfallibleAppend(swo); + } + aBuffer.Remove(aEntry); + if (mBudget) { + MaybeKillObject(swo); + } + } + return true; + } + + bool HasSnowWhiteObjects() const { return !mObjects.IsEmpty(); } + + bool SawSnowWhiteObjects() const { return mSawSnowWhiteObjects; } + + virtual void Trace(JS::Heap<JS::Value>* aValue, const char* aName, + void* aClosure) const override { + const JS::Value& val = aValue->unbarrieredGet(); + if (val.isGCThing() && 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(nsWrapperCache* aWrapperCache, const char* aName, + void* aClosure) const override { + AppendJSObjectToPurpleBuffer(aWrapperCache->GetWrapperPreserveColor()); + } + + 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; + js::SliceBudget* mBudget; + bool mSawSnowWhiteObjects; +}; + +class RemoveSkippableVisitor : public SnowWhiteKiller { + public: + RemoveSkippableVisitor(nsCycleCollector* aCollector, js::SliceBudget& aBudget, + bool aRemoveChildlessNodes, + bool aAsyncSnowWhiteFreeing, + CC_ForgetSkippableCallback aCb) + : SnowWhiteKiller(aCollector), + mBudget(aBudget), + 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); + } + } + + bool Visit(nsPurpleBuffer& aBuffer, nsPurpleBufferEntry* aEntry) { + if (mBudget.isOverBudget()) { + return false; + } + + // CanSkip calls can be a bit slow, so increase the likelihood that + // isOverBudget actually checks whether we're over the time budget. + mBudget.step(5); + 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 true; + } + void* o = aEntry->mObject; + nsCycleCollectionParticipant* cp = aEntry->mParticipant; + ToParticipant(o, &cp); + if (aEntry->mRefCnt->IsPurple() && !cp->CanSkip(o, false) && + (!mRemoveChildlessNodes || MayHaveChild(o, cp))) { + return true; + } + aBuffer.Remove(aEntry); + return true; + } + + private: + js::SliceBudget& mBudget; + bool mRemoveChildlessNodes; + bool mAsyncSnowWhiteFreeing; + bool mDispatchedDeferredDeletion; + CC_ForgetSkippableCallback mCallback; +}; + +void nsPurpleBuffer::RemoveSkippable(nsCycleCollector* aCollector, + js::SliceBudget& aBudget, + bool aRemoveChildlessNodes, + bool aAsyncSnowWhiteFreeing, + CC_ForgetSkippableCallback aCb) { + RemoveSkippableVisitor visitor(aCollector, aBudget, aRemoveChildlessNodes, + aAsyncSnowWhiteFreeing, aCb); + VisitEntries(visitor); +} + +bool nsCycleCollector::FreeSnowWhite(bool aUntilNoSWInPurpleBuffer) { + CheckThreadSafety(); + + if (mFreeingSnowWhite) { + return false; + } + + AUTO_PROFILER_LABEL_CATEGORY_PAIR(GCCC_FreeSnowWhite); + + 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; +} + +bool nsCycleCollector::FreeSnowWhiteWithBudget(js::SliceBudget& aBudget) { + CheckThreadSafety(); + + if (mFreeingSnowWhite) { + return false; + } + + AUTO_PROFILER_LABEL_CATEGORY_PAIR(GCCC_FreeSnowWhite); + AutoRestore<bool> ar(mFreeingSnowWhite); + mFreeingSnowWhite = true; + + SnowWhiteKiller visitor(this, &aBudget); + mPurpleBuf.VisitEntries(visitor); + return visitor.SawSnowWhiteObjects(); + ; +} + +void nsCycleCollector::ForgetSkippable(js::SliceBudget& aBudget, + bool aRemoveChildlessNodes, + bool aAsyncSnowWhiteFreeing) { + CheckThreadSafety(); + + if (mFreeingSnowWhite) { + return; + } + + // 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 (mCCJSRuntime) { + mCCJSRuntime->PrepareForForgetSkippable(); + } + MOZ_ASSERT( + !mScanInProgress, + "Don't forget skippable or free snow-white while scan is in progress."); + mPurpleBuf.RemoveSkippable(this, aBudget, 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); + + AUTO_PROFILER_LABEL_CATEGORY_PAIR(GCCC_BuildGraph); + JS::AutoEnterCycleCollection autocc(Runtime()->Runtime()); + 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) {} + + bool 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; + + MOZ_ASSERT( + aEntry->mParticipant || + CanonicalizeXPCOMParticipant(static_cast<nsISupports*>(obj)) == obj, + "Suspect nsISupports pointer must be canonical"); + + PtrInfo* pi = mGraph.FindNode(obj); + if (!pi) { + return true; + } + 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 true; + } + FloodBlackNode(mCount, mFailed, pi); + return true; + } + + 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 hasJSRuntime = !!mCCJSRuntime; + nsCycleCollectionParticipant* jsParticipant = + hasJSRuntime ? mCCJSRuntime->GCThingParticipant() : nullptr; + nsCycleCollectionParticipant* zoneParticipant = + hasJSRuntime ? mCCJSRuntime->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(hasJSRuntime)) { + // 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; + } + + pi->AnnotatedReleaseAssert( + pi->mInternalRefs <= pi->mRefCount, + "More references to an object than its refcount"); + + // 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(Runtime()->Runtime()); + + 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 hasJSRuntime = !!mCCJSRuntime; + nsCycleCollectionParticipant* zoneParticipant = + hasJSRuntime ? mCCJSRuntime->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(mCCJSRuntime); + ++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); + } + mCCJSRuntime->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 (mCCJSRuntime) { + mCCJSRuntime->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), + mCCJSRuntime(nullptr), + mIncrementalPhase(IdlePhase), +#ifdef DEBUG + mEventTarget(GetCurrentSerialEventTarget()), +#endif + mWhiteNodeCount(0), + mBeforeUnlinkCB(nullptr), + mForgetSkippableCB(nullptr), + mUnmergedNeeded(0), + mMergedInARow(0) { +} + +nsCycleCollector::~nsCycleCollector() { + MOZ_ASSERT(!mJSPurpleBuffer, "Didn't call JSPurpleBuffer::Destroy?"); + + UnregisterWeakMemoryReporter(this); +} + +void nsCycleCollector::SetCCJSRuntime(CycleCollectedJSRuntime* aCCRuntime) { + MOZ_RELEASE_ASSERT( + !mCCJSRuntime, + "Multiple registrations of CycleCollectedJSRuntime in cycle collector"); + mCCJSRuntime = aCCRuntime; + + 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::ClearCCJSRuntime() { + MOZ_RELEASE_ASSERT(mCCJSRuntime, + "Clearing CycleCollectedJSRuntime in cycle collector " + "before a runtime was registered"); + mCCJSRuntime = 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"); + + MOZ_ASSERT(aParti || CanonicalizeXPCOMParticipant( + static_cast<nsISupports*>(aPtr)) == aPtr, + "Suspect nsISupports pointer must be canonical"); + + mPurpleBuf.Put(aPtr, aParti, aRefCnt); +} + +void nsCycleCollector::SuspectNurseryEntries() { + MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); + while (gNurseryPurpleBufferEntryCount) { + NurseryPurpleBufferEntry& entry = + gNurseryPurpleBufferEntry[--gNurseryPurpleBufferEntryCount]; + mPurpleBuf.Put(entry.mPtr, entry.mParticipant, entry.mRefCnt); + } +} + +void nsCycleCollector::CheckThreadSafety() { +#ifdef DEBUG + MOZ_ASSERT(mEventTarget->IsOnCurrentThread()); +#endif +} + +// The cycle collector uses the mark bitmap to discover what JS objects are +// reachable only from XPConnect roots that might participate in cycles. We ask +// the JS runtime whether we need to force a GC before this CC. It should only +// be true when UnmarkGray has run out of stack. We also force GCs on shutdown +// to collect cycles involving both DOM and JS, and in WantAllTraces CCs to +// prevent hijinks from ForgetSkippable and compartmental GCs. +void nsCycleCollector::FixGrayBits(bool aIsShutdown, TimeLog& aTimeLog) { + CheckThreadSafety(); + + if (!mCCJSRuntime) { + return; + } + + // If we're not forcing a GC anyways due to shutdown or an all traces CC, + // check to see if we still need to do one to fix the gray bits. + if (!(aIsShutdown || (mLogger && mLogger->IsAllTraces()))) { + mCCJSRuntime->FixWeakMappingGrayBits(); + aTimeLog.Checkpoint("FixWeakMappingGrayBits"); + + bool needGC = !mCCJSRuntime->AreGCGrayBitsValid(); + // Only do a telemetry ping for non-shutdown CCs. + CC_TELEMETRY(_NEED_GC, needGC); + if (!needGC) { + return; + } + } + + mResults.mForcedGC = true; + + uint32_t count = 0; + do { + if (aIsShutdown) { + mCCJSRuntime->GarbageCollect(JS::GCOptions::Shutdown, + JS::GCReason::SHUTDOWN_CC); + } else { + mCCJSRuntime->GarbageCollect(JS::GCOptions::Normal, + JS::GCReason::CC_FORCED); + } + + mCCJSRuntime->FixWeakMappingGrayBits(); + + // It's possible that FixWeakMappingGrayBits will hit OOM when unmarking + // gray and we will have to go round again. The second time there should not + // be any weak mappings to fix up so the loop body should run at most twice. + MOZ_RELEASE_ASSERT(count < 2); + count++; + } while (!mCCJSRuntime->AreGCGrayBitsValid()); + + aTimeLog.Checkpoint("FixGrayBits"); +} + +bool nsCycleCollector::IsIncrementalGCInProgress() { + return mCCJSRuntime && JS::IsIncrementalGCInProgress(mCCJSRuntime->Runtime()); +} + +void nsCycleCollector::FinishAnyIncrementalGCInProgress() { + if (IsIncrementalGCInProgress()) { + NS_WARNING("Finishing incremental GC in progress during CC"); + JSContext* cx = CycleCollectedJSContext::Get()->Context(); + JS::PrepareForIncrementalGC(cx); + JS::FinishIncrementalGC(cx, JS::GCReason::CC_FORCED); + } +} + +void nsCycleCollector::CleanupAfterCollection() { + TimeLog timeLog; + MOZ_ASSERT(mIncrementalPhase == CleanupPhase); + MOZ_RELEASE_ASSERT(!mScanInProgress); + 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 (mCCJSRuntime) { + mCCJSRuntime->FinalizeDeferredThings( + mResults.mAnyManual ? CycleCollectedJSRuntime::FinalizeNow + : CycleCollectedJSRuntime::FinalizeIncrementally); + mCCJSRuntime->EndCycleCollectionCallback(mResults); + timeLog.Checkpoint("CleanupAfterCollection::EndCycleCollectionCallback()"); + } + mIncrementalPhase = IdlePhase; +} + +void nsCycleCollector::ShutdownCollect() { + FinishAnyIncrementalGCInProgress(); + CycleCollectedJSContext* ccJSContext = CycleCollectedJSContext::Get(); + JS::ShutdownAsyncTasks(ccJSContext->Context()); + + SliceBudget unlimitedBudget = SliceBudget::unlimited(); + uint32_t i; + bool collectedAny = true; + for (i = 0; i < DEFAULT_SHUTDOWN_COLLECTIONS && collectedAny; ++i) { + collectedAny = Collect(CCReason::SHUTDOWN, ccIsManual::CCIsManual, + unlimitedBudget, nullptr); + // Run any remaining tasks that may have been enqueued via RunInStableState + // or DispatchToMicroTask. These can hold alive CCed objects, and we want to + // clear them out before we run the CC again or finish shutting down. + ccJSContext->PerformMicroTaskCheckPoint(true); + ccJSContext->ProcessStableStateQueue(); + } + + // This warning would happen very frequently, so don't do it unless we're + // logging this CC, so we might care about how many CCs there are. + NS_WARNING_ASSERTION( + !mParams.LogThisCC(mShutdownCount) || 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(CCReason aReason, ccIsManual aIsManual, + SliceBudget& aBudget, + nsICycleCollectorListener* aManualListener, + bool aPreferShorterSlices) { + AUTO_PROFILER_LABEL_RELEVANT_FOR_JS("Incremental CC", GCCC); + + CheckThreadSafety(); + + // This can legitimately happen in a few cases. See bug 383651. + if (mActivelyCollecting || mFreeingSnowWhite) { + return false; + } + mActivelyCollecting = true; + + MOZ_ASSERT(!IsIncrementalGCInProgress()); + + 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 (aIsManual == ccIsManual::CCIsManual) { + mResults.mAnyManual = true; + } + + ++mResults.mNumSlices; + + bool continueSlice = aBudget.isUnlimited() || !aPreferShorterSlices; + do { + switch (mIncrementalPhase) { + case IdlePhase: + PrintPhase("BeginCollection"); + BeginCollection(aReason, aIsManual, 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. + { + AUTO_PROFILER_LABEL_CATEGORY_PAIR(GCCC_ScanRoots); + PrintPhase("ScanRoots"); + ScanRoots(startedIdle); + } + { + AUTO_PROFILER_LABEL_CATEGORY_PAIR(GCCC_CollectWhite); + PrintPhase("CollectWhite"); + collectedAny = CollectWhite(); + } + break; + case CleanupPhase: + PrintPhase("CleanupAfterCollection"); + CleanupAfterCollection(); + continueSlice = false; + break; + } + if (continueSlice) { + aBudget.forceCheck(); + continueSlice = !aBudget.isOverBudget(); + } + } while (continueSlice); + + // Clear mActivelyCollecting here to ensure that a recursive call to + // Collect() does something. + mActivelyCollecting = false; + + if (aIsManual && !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(aReason, ccIsManual::CCIsManual, aBudget, aManualListener)) { + collectedAny = true; + } + } + + MOZ_ASSERT_IF(aIsManual == CCIsManual, 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(CCReason::GC_WAITING); +} + +void nsCycleCollector::FinishAnyCurrentCollection(CCReason aReason) { + if (IsIdle()) { + return; + } + + SliceBudget unlimitedBudget = SliceBudget::unlimited(); + PrintPhase("FinishAnyCurrentCollection"); + // Use CCIsNotManual because we only want to finish the CC in progress. + Collect(aReason, ccIsManual::CCIsNotManual, 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(ccIsManual aIsManual) { + if (!mCCJSRuntime) { + 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 (aIsManual == CCIsNotManual && mCCJSRuntime->UsefulToMergeZones()) { + mMergedInARow++; + return true; + } else { + mMergedInARow = 0; + return false; + } +} + +void nsCycleCollector::BeginCollection( + CCReason aReason, ccIsManual aIsManual, + nsICycleCollectorListener* aManualListener) { + TimeLog timeLog; + MOZ_ASSERT(IsIdle()); + MOZ_RELEASE_ASSERT(!mScanInProgress); + + mCollectionStart = TimeStamp::Now(); + + if (mCCJSRuntime) { + mCCJSRuntime->BeginCycleCollectionCallback(aReason); + timeLog.Checkpoint("BeginCycleCollectionCallback()"); + } + + bool isShutdown = (aReason == CCReason::SHUTDOWN); + if (isShutdown) { + mShutdownCount += 1; + } + + // 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(mShutdownCount)) { + mLogger = new nsCycleCollectorLogger(); + if (mParams.AllTracesThisCC(isShutdown)) { + mLogger->SetAllTraces(); + } + } + + // BeginCycleCollectionCallback() might have started an IGC, and we need + // to finish it before we run FixGrayBits. + FinishAnyIncrementalGCInProgress(); + timeLog.Checkpoint("Pre-FixGrayBits finish IGC"); + + FixGrayBits(isShutdown, timeLog); + if (mCCJSRuntime) { + mCCJSRuntime->CheckGrayBits(); + } + + 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(mCCJSRuntime->Runtime()); + mGraph.Init(); + mResults.Init(); + mResults.mSuspectedAtCCStart = SuspectedCount(); + mResults.mAnyManual = aIsManual; + bool mergeZones = ShouldMergeZones(aIsManual); + mResults.mMergedZones = mergeZones; + + MOZ_ASSERT(!mBuilder, "Forgot to clear mBuilder"); + mBuilder = MakeUnique<CCGraphBuilder>(mGraph, mResults, mCCJSRuntime, mLogger, + mergeZones); + timeLog.Checkpoint("BeginCollection prepare graph builder"); + + if (mCCJSRuntime) { + mCCJSRuntime->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(); + if (NS_IsMainThread()) { + return gNurseryPurpleBufferEntryCount + mPurpleBuf.Count(); + } + + return mPurpleBuf.Count(); +} + +void nsCycleCollector::Shutdown(bool aDoCollect) { + CheckThreadSafety(); + + if (NS_IsMainThread()) { + gNurseryPurpleBufferEnabled = false; + } + + // Always delete snow white objects. + FreeSnowWhite(true); + + if (aDoCollect) { + ShutdownCollect(); + } + + if (mJSPurpleBuffer) { + mJSPurpleBuffer->Destroy(); + } +} + +void nsCycleCollector::RemoveObjectFromGraph(void* aObj) { + if (IsIdle()) { + return; + } + + mGraph.RemoveObjectFromMap(aObj); + if (mBuilder) { + mBuilder->RemoveCachedEntry(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: + // - mCCJSRuntime: 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->SetCCJSRuntime(aCx->Runtime()); +} + +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->ClearCCJSRuntime(); + 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. + ToParticipant(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) { + if (( +#ifdef HAVE_64BIT_BUILD + aRefCnt->IsOnMainThread() || +#endif + NS_IsMainThread()) && + gNurseryPurpleBufferEnabled) { + // The next time the object is passed to the purple buffer, we can do faster + // IsOnMainThread() check. + aRefCnt->SetIsOnMainThread(); + SuspectUsingNurseryPurpleBuffer(aPtr, aCp, aRefCnt); + return; + } + + CollectorData* data = sCollectorData.get(); + + // This assertion will happen if you AddRef or Release a cycle collected + // object on a thread that does not have an active cycle collector. + // This can happen in a few situations: + // 1. We never cycle collect on this thread. (The cycle collector is only + // run on the main thread and DOM worker threads.) + // 2. The cycle collector hasn't been initialized on this thread yet. + // 3. The cycle collector has already been shut down on this thread. + MOZ_DIAGNOSTIC_ASSERT( + data, + "Cycle collected object used on a thread without a cycle collector."); + + if (MOZ_LIKELY(data->mCollector)) { + data->mCollector->Suspect(aPtr, aCp, aRefCnt); + return; + } + SuspectAfterShutdown(aPtr, aCp, aRefCnt, aShouldDelete); +} + +void ClearNurseryPurpleBuffer() { + MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); + CollectorData* data = sCollectorData.get(); + MOZ_ASSERT(data); + MOZ_ASSERT(data->mCollector); + data->mCollector->SuspectNurseryEntries(); +} + +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(js::SliceBudget& aBudget, + bool aRemoveChildlessNodes, + bool aAsyncSnowWhiteFreeing) { + CollectorData* data = sCollectorData.get(); + + // We should have started the cycle collector by now. + MOZ_ASSERT(data); + MOZ_ASSERT(data->mCollector); + + TimeLog timeLog; + data->mCollector->ForgetSkippable(aBudget, aRemoveChildlessNodes, + aAsyncSnowWhiteFreeing); + timeLog.Checkpoint("ForgetSkippable()"); +} + +void nsCycleCollector_dispatchDeferredDeletion(bool aContinuation, + bool aPurge) { + CycleCollectedJSRuntime* rt = CycleCollectedJSRuntime::Get(); + if (rt) { + rt->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); +} + +bool nsCycleCollector_doDeferredDeletionWithBudget(js::SliceBudget& aBudget) { + 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->FreeSnowWhiteWithBudget(aBudget); +} + +already_AddRefed<nsICycleCollectorLogSink> nsCycleCollector_createLogSink() { + nsCOMPtr<nsICycleCollectorLogSink> sink = new nsCycleCollectorLogSinkToFile(); + return sink.forget(); +} + +bool nsCycleCollector_collect(CCReason aReason, + nsICycleCollectorListener* aManualListener) { + CollectorData* data = sCollectorData.get(); + + // We should have started the cycle collector by now. + MOZ_ASSERT(data); + MOZ_ASSERT(data->mCollector); + + AUTO_PROFILER_LABEL("nsCycleCollector_collect", GCCC); + + SliceBudget unlimitedBudget = SliceBudget::unlimited(); + return data->mCollector->Collect(aReason, ccIsManual::CCIsManual, + unlimitedBudget, aManualListener); +} + +void nsCycleCollector_collectSlice(SliceBudget& budget, CCReason aReason, + bool aPreferShorterSlices) { + CollectorData* data = sCollectorData.get(); + + // We should have started the cycle collector by now. + MOZ_ASSERT(data); + MOZ_ASSERT(data->mCollector); + + AUTO_PROFILER_LABEL("nsCycleCollector_collectSlice", GCCC); + + data->mCollector->Collect(aReason, ccIsManual::CCIsNotManual, 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(CCReason::API); +} + +void nsCycleCollector_shutdown(bool aDoCollect) { + CollectorData* data = sCollectorData.get(); + + if (data) { + MOZ_ASSERT(data->mCollector); + AUTO_PROFILER_LABEL("nsCycleCollector_shutdown", OTHER); + + { + RefPtr<nsCycleCollector> collector = data->mCollector; + collector->Shutdown(aDoCollect); + data->mCollector = nullptr; + } + + 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 0000000000..1c583e04cf --- /dev/null +++ b/xpcom/base/nsCycleCollector.h @@ -0,0 +1,74 @@ +/* -*- 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 <cstdint> +#include "mozilla/Attributes.h" + +namespace js { +class SliceBudget; +} + +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(js::SliceBudget& aBudget, + 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(); +bool nsCycleCollector_doDeferredDeletionWithBudget(js::SliceBudget& aBudget); + +already_AddRefed<nsICycleCollectorLogSink> nsCycleCollector_createLogSink(); +already_AddRefed<nsICycleCollectorListener> nsCycleCollector_createLogger(); + +// Run a cycle collection and return whether anything was collected. +bool nsCycleCollector_collect(mozilla::CCReason aReason, + nsICycleCollectorListener* aManualListener); + +void nsCycleCollector_collectSlice(js::SliceBudget& budget, + mozilla::CCReason aReason, + 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. +MOZ_CAN_RUN_SCRIPT +void nsCycleCollector_shutdown(bool aDoCollect = true); + +// Helpers for interacting with JS +void nsCycleCollector_registerJSContext(mozilla::CycleCollectedJSContext* aCx); +void nsCycleCollector_forgetJSContext(); + +#endif // nsCycleCollector_h__ diff --git a/xpcom/base/nsCycleCollectorTraceJSHelpers.cpp b/xpcom/base/nsCycleCollectorTraceJSHelpers.cpp new file mode 100644 index 0000000000..02bc92ddab --- /dev/null +++ b/xpcom/base/nsCycleCollectorTraceJSHelpers.cpp @@ -0,0 +1,89 @@ +/* -*- 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 "nsString.h" +#include "nsWrapperCacheInlines.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 nsCycleCollectionParticipant::NoteJSChild(JS::GCCellPtr aGCThing, + const char* aName, + void* aClosure) { + nsCycleCollectionTraversalCallback* cb = + static_cast<nsCycleCollectionTraversalCallback*>(aClosure); + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*cb, aName); + if (JS::IsCCTraceKind(aGCThing.kind())) { + cb->NoteJSChild(aGCThing); + } +} + +void TraceCallbackFunc::Trace(JS::Heap<JS::Value>* aPtr, const char* aName, + void* aClosure) const { + if (aPtr->unbarrieredGet().isGCThing()) { + mCallback(aPtr->unbarrieredGet().toGCCellPtr(), aName, aClosure); + } +} + +void TraceCallbackFunc::Trace(JS::Heap<jsid>* aPtr, const char* aName, + void* aClosure) const { + if (aPtr->unbarrieredGet().isGCThing()) { + mCallback(aPtr->unbarrieredGet().toGCCellPtr(), 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(nsWrapperCache* aPtr, const char* aName, + void* aClosure) const { + JSObject* obj = aPtr->GetWrapperPreserveColor(); + if (obj) { + mCallback(JS::GCCellPtr(obj), 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/nsDebug.h b/xpcom/base/nsDebug.h new file mode 100644 index 0000000000..3e3fc4d89e --- /dev/null +++ b/xpcom/base/nsDebug.h @@ -0,0 +1,330 @@ +/* -*- 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 nsDebug_h___ +#define nsDebug_h___ + +#include "nscore.h" +#include "nsError.h" + +#include "nsXPCOM.h" +#include "mozilla/Assertions.h" +#include "mozilla/glue/Debug.h" +#include "mozilla/DbgMacro.h" +#include "mozilla/Likely.h" +#include <stdarg.h> + +#ifdef DEBUG +# include "mozilla/ErrorNames.h" +# include "mozilla/IntegerPrintfMacros.h" +# include "mozilla/Printf.h" +#endif + +/** + * Warn if the given condition is true. The condition is evaluated in both + * release and debug builds, and the result is an expression which can be + * used in subsequent expressions, such as: + * + * if (NS_WARN_IF(NS_FAILED(rv)) { + * return rv; + * } + * + * This explicit warning and return is preferred to the NS_ENSURE_* macros + * which hide the warning and the return control flow. + * + * This macro can also be used outside of conditions just to issue a warning, + * like so: + * + * Unused << NS_WARN_IF(NS_FAILED(FnWithSideEffects()); + * + * (The |Unused <<| is necessary because of the [[nodiscard]] annotation.) + * + * However, note that the argument to this macro is evaluated in all builds. If + * you just want a warning assertion, it is better to use NS_WARNING_ASSERTION + * (which evaluates the condition only in debug builds) like so: + * + * NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "operation failed"); + * + * @note This is C++-only + */ +#ifdef __cplusplus +# ifdef DEBUG +[[nodiscard]] inline bool NS_warn_if_impl(bool aCondition, const char* aExpr, + const char* aFile, int32_t aLine) { + if (MOZ_UNLIKELY(aCondition)) { + NS_DebugBreak(NS_DEBUG_WARNING, nullptr, aExpr, aFile, aLine); + } + return aCondition; +} +# define NS_WARN_IF(condition) \ + NS_warn_if_impl(condition, #condition, __FILE__, __LINE__) +# else +# define NS_WARN_IF(condition) (bool)(condition) +# endif +#endif + +/** + * Test an assertion for truth. If the expression is not true then + * emit a warning. + * + * Program execution continues past the usage of this macro. + * + * Note also that the non-debug version of this macro does <b>not</b> + * evaluate the message argument. + */ +#ifdef DEBUG +# define NS_WARNING_ASSERTION(_expr, _msg) \ + do { \ + if (!(_expr)) { \ + NS_DebugBreak(NS_DEBUG_WARNING, _msg, #_expr, __FILE__, __LINE__); \ + } \ + } while (false) +#else +# define NS_WARNING_ASSERTION(_expr, _msg) \ + do { /* nothing */ \ + } while (false) +#endif + +/** + * Test an assertion for truth. If the expression is not true then + * trigger a program failure. + * + * Note that the non-debug version of this macro does <b>not</b> + * evaluate the message argument. + */ +#ifdef DEBUG +inline void MOZ_PretendNoReturn() MOZ_PRETEND_NORETURN_FOR_STATIC_ANALYSIS {} +# define NS_ASSERTION(expr, str) \ + do { \ + if (!(expr)) { \ + NS_DebugBreak(NS_DEBUG_ASSERTION, str, #expr, __FILE__, __LINE__); \ + MOZ_PretendNoReturn(); \ + } \ + } while (0) +#else +# define NS_ASSERTION(expr, str) \ + do { /* nothing */ \ + } while (0) +#endif + +/** + * Log an error message. + */ +#ifdef DEBUG +# define NS_ERROR(str) \ + do { \ + NS_DebugBreak(NS_DEBUG_ASSERTION, str, "Error", __FILE__, __LINE__); \ + MOZ_PretendNoReturn(); \ + } while (0) +#else +# define NS_ERROR(str) \ + do { /* nothing */ \ + } while (0) +#endif + +/** + * Log a warning message. + */ +#ifdef DEBUG +# define NS_WARNING(str) \ + NS_DebugBreak(NS_DEBUG_WARNING, str, nullptr, __FILE__, __LINE__) +#else +# define NS_WARNING(str) \ + do { /* nothing */ \ + } while (0) +#endif + +/** + * Trigger a debugger breakpoint, only in debug builds. + */ +#ifdef DEBUG +# define NS_BREAK() \ + do { \ + NS_DebugBreak(NS_DEBUG_BREAK, nullptr, nullptr, __FILE__, __LINE__); \ + MOZ_PretendNoReturn(); \ + } while (0) +#else +# define NS_BREAK() \ + do { /* nothing */ \ + } while (0) +#endif + +/****************************************************************************** +** Macros for static assertions. These are used by the sixgill tool. +** When the tool is not running these macros are no-ops. +******************************************************************************/ + +/* Avoid name collision if included with other headers defining annotations. */ +#ifndef HAVE_STATIC_ANNOTATIONS +# define HAVE_STATIC_ANNOTATIONS + +# ifdef XGILL_PLUGIN + +# define STATIC_PRECONDITION(COND) __attribute__((precondition(#COND))) +# define STATIC_PRECONDITION_ASSUME(COND) \ + __attribute__((precondition_assume(#COND))) +# define STATIC_POSTCONDITION(COND) __attribute__((postcondition(#COND))) +# define STATIC_POSTCONDITION_ASSUME(COND) \ + __attribute__((postcondition_assume(#COND))) +# define STATIC_INVARIANT(COND) __attribute__((invariant(#COND))) +# define STATIC_INVARIANT_ASSUME(COND) \ + __attribute__((invariant_assume(#COND))) + +/* Used to make identifiers for assert/assume annotations in a function. */ +# define STATIC_PASTE2(X, Y) X##Y +# define STATIC_PASTE1(X, Y) STATIC_PASTE2(X, Y) + +# define STATIC_ASSUME(COND) \ + do { \ + __attribute__((assume_static(#COND), unused)) int STATIC_PASTE1( \ + assume_static_, __COUNTER__); \ + } while (false) + +# define STATIC_ASSERT_RUNTIME(COND) \ + do { \ + __attribute__((assert_static_runtime(#COND), \ + unused)) int STATIC_PASTE1(assert_static_runtime_, \ + __COUNTER__); \ + } while (false) + +# else /* XGILL_PLUGIN */ + +# define STATIC_PRECONDITION(COND) /* nothing */ +# define STATIC_PRECONDITION_ASSUME(COND) /* nothing */ +# define STATIC_POSTCONDITION(COND) /* nothing */ +# define STATIC_POSTCONDITION_ASSUME(COND) /* nothing */ +# define STATIC_INVARIANT(COND) /* nothing */ +# define STATIC_INVARIANT_ASSUME(COND) /* nothing */ + +# define STATIC_ASSUME(COND) \ + do { /* nothing */ \ + } while (false) +# define STATIC_ASSERT_RUNTIME(COND) \ + do { /* nothing */ \ + } while (false) + +# endif /* XGILL_PLUGIN */ + +# define STATIC_SKIP_INFERENCE STATIC_INVARIANT(skip_inference()) + +#endif /* HAVE_STATIC_ANNOTATIONS */ + +/****************************************************************************** +** Macros for terminating execution when an unrecoverable condition is +** reached. These need to be compiled regardless of the DEBUG flag. +******************************************************************************/ + +/* Macros for checking the trueness of an expression passed in within an + * interface implementation. These need to be compiled regardless of the + * DEBUG flag. New code should use NS_WARN_IF(condition) instead! + * @status deprecated + */ + +#define NS_ENSURE_TRUE(x, ret) \ + do { \ + if (MOZ_UNLIKELY(!(x))) { \ + NS_WARNING("NS_ENSURE_TRUE(" #x ") failed"); \ + return ret; \ + } \ + } while (false) + +#define NS_ENSURE_FALSE(x, ret) NS_ENSURE_TRUE(!(x), ret) + +#define NS_ENSURE_TRUE_VOID(x) \ + do { \ + if (MOZ_UNLIKELY(!(x))) { \ + NS_WARNING("NS_ENSURE_TRUE(" #x ") failed"); \ + return; \ + } \ + } while (false) + +#define NS_ENSURE_FALSE_VOID(x) NS_ENSURE_TRUE_VOID(!(x)) + +/****************************************************************************** +** Macros for checking results +******************************************************************************/ + +#if defined(DEBUG) && !defined(XPCOM_GLUE_AVOID_NSPR) + +# define NS_ENSURE_SUCCESS_BODY(res, ret) \ + const char* name = mozilla::GetStaticErrorName(__rv); \ + mozilla::SmprintfPointer msg = mozilla::Smprintf( \ + "NS_ENSURE_SUCCESS(%s, %s) failed with " \ + "result 0x%" PRIX32 "%s%s%s", \ + #res, #ret, static_cast<uint32_t>(__rv), name ? " (" : "", \ + name ? name : "", name ? ")" : ""); \ + NS_WARNING(msg.get()); + +# define NS_ENSURE_SUCCESS_BODY_VOID(res) \ + const char* name = mozilla::GetStaticErrorName(__rv); \ + mozilla::SmprintfPointer msg = mozilla::Smprintf( \ + "NS_ENSURE_SUCCESS_VOID(%s) failed with " \ + "result 0x%" PRIX32 "%s%s%s", \ + #res, static_cast<uint32_t>(__rv), name ? " (" : "", name ? name : "", \ + name ? ")" : ""); \ + NS_WARNING(msg.get()); + +#else + +# define NS_ENSURE_SUCCESS_BODY(res, ret) \ + NS_WARNING("NS_ENSURE_SUCCESS(" #res ", " #ret ") failed"); + +# define NS_ENSURE_SUCCESS_BODY_VOID(res) \ + NS_WARNING("NS_ENSURE_SUCCESS_VOID(" #res ") failed"); + +#endif + +#define NS_ENSURE_SUCCESS(res, ret) \ + do { \ + nsresult __rv = res; /* Don't evaluate |res| more than once */ \ + if (NS_FAILED(__rv)) { \ + NS_ENSURE_SUCCESS_BODY(res, ret) \ + return ret; \ + } \ + } while (false) + +#define NS_ENSURE_SUCCESS_VOID(res) \ + do { \ + nsresult __rv = res; \ + if (NS_FAILED(__rv)) { \ + NS_ENSURE_SUCCESS_BODY_VOID(res) \ + return; \ + } \ + } while (false) + +/****************************************************************************** +** Macros for checking state and arguments upon entering interface boundaries +******************************************************************************/ + +#define NS_ENSURE_ARG(arg) NS_ENSURE_TRUE(arg, NS_ERROR_INVALID_ARG) + +#define NS_ENSURE_ARG_POINTER(arg) NS_ENSURE_TRUE(arg, NS_ERROR_INVALID_POINTER) + +#define NS_ENSURE_ARG_MIN(arg, min) \ + NS_ENSURE_TRUE((arg) >= min, NS_ERROR_INVALID_ARG) + +#define NS_ENSURE_ARG_MAX(arg, max) \ + NS_ENSURE_TRUE((arg) <= max, NS_ERROR_INVALID_ARG) + +#define NS_ENSURE_ARG_RANGE(arg, min, max) \ + NS_ENSURE_TRUE(((arg) >= min) && ((arg) <= max), NS_ERROR_INVALID_ARG) + +#define NS_ENSURE_STATE(state) NS_ENSURE_TRUE(state, NS_ERROR_UNEXPECTED) + +/*****************************************************************************/ + +#if (defined(DEBUG) || (defined(NIGHTLY_BUILD) && !defined(MOZ_PROFILING))) && \ + !defined(XPCOM_GLUE_AVOID_NSPR) +# define MOZ_THREAD_SAFETY_OWNERSHIP_CHECKS_SUPPORTED 1 +#endif + +#ifdef MOZILLA_INTERNAL_API +void NS_ABORT_OOM(size_t aSize); +#else +inline void NS_ABORT_OOM(size_t) { MOZ_CRASH(); } +#endif + +#endif /* nsDebug_h___ */ diff --git a/xpcom/base/nsDebugImpl.cpp b/xpcom/base/nsDebugImpl.cpp new file mode 100644 index 0000000000..4023efd0ec --- /dev/null +++ b/xpcom/base/nsDebugImpl.cpp @@ -0,0 +1,675 @@ +/* -*- 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 "mozilla/IntentionalCrash.h" +#include "mozilla/Printf.h" +#include "mozilla/ProfilerMarkers.h" + +#include "MainThreadUtils.h" +#include "nsDebugImpl.h" +#include "nsDebug.h" +#include "nsExceptionHandler.h" +#include "nsString.h" +#include "nsXULAppAPI.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 "mozilla/StackWalk.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 + +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 +#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::CrashWithOOM() { + NS_ABORT_OOM(-1); + return NS_OK; +} + +// From toolkit/library/rust/lib.rs +extern "C" void intentional_panic(const char* message); + +NS_IMETHODIMP +nsDebugImpl::RustPanic(const char* aMessage) { + intentional_panic(aMessage); + return NS_OK; +} + +// From toolkit/library/rust/lib.rs +extern "C" void debug_log(const char* target, const char* message); + +NS_IMETHODIMP +nsDebugImpl::RustLog(const char* aTarget, const char* aMessage) { + debug_log(aTarget, aMessage); + 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(__OpenBSD__) && defined(MOZ_SANDBOX) + // no access to KERN_PROC_PID sysctl when pledge'd + return NS_OK; +#endif +#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; +} + +/* static */ const char* nsDebugImpl::GetMultiprocessMode() { + return sMultiprocessDescription; +} + +/** + * 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 final : public mozilla::PrintfTarget { + FixedBuffer() : curlen(0) { buffer[0] = '\0'; } + + char buffer[764]; + uint32_t curlen; + + bool append(const char* sp, size_t len) override; +}; + +bool FixedBuffer::append(const char* aBuf, size_t aLen) { + if (!aLen) { + return true; + } + + if (curlen + aLen >= sizeof(buffer)) { + aLen = sizeof(buffer) - curlen - 1; + } + + if (aLen) { + memcpy(buffer + curlen, aBuf, aLen); + curlen += aLen; + buffer[curlen] = '\0'; + } + + return true; +} + +namespace geckoprofiler::markers { + +struct DebugBreakMarker { + static constexpr Span<const char> MarkerTypeName() { + return MakeStringSpan("DebugBreak"); + } + static void StreamJSONMarkerData(baseprofiler::SpliceableJSONWriter& aWriter, + uint32_t aSeverity, + const ProfilerString8View& aStr, + const ProfilerString8View& aExpr, + const ProfilerString8View& aFile, + int32_t aLine) { + nsAutoCString 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; + } + aWriter.StringProperty("Severity", sevString); + // The 'name' property is searchable on the front-end. + if (aStr.Length() != 0) { + aWriter.StringProperty("Message", aStr); + aWriter.StringProperty("name", aStr); + } else if (aExpr.Length() != 0) { + aWriter.StringProperty("name", aExpr); + } + if (aExpr.Length() != 0) { + aWriter.StringProperty("Expression", aExpr); + } + if (aFile.Length() != 0) { + aWriter.StringProperty("File", aFile); + } + if (aLine != 0) { + aWriter.IntProperty("Line", aLine); + } + } + static MarkerSchema MarkerTypeDisplay() { + using MS = MarkerSchema; + MS schema{MS::Location::TimelineOverview, MS::Location::MarkerChart, + MS::Location::MarkerTable}; + schema.SetAllLabels("{marker.data.Severity}: {marker.data.name}"); + schema.AddKeyFormat("Message", MS::Format::String); + schema.AddKeyFormat("Severity", MS::Format::String); + schema.AddKeyFormat("Expression", MS::Format::String); + schema.AddKeyFormat("File", MS::Format::String); + schema.AddKeyFormat("Line", MS::Format::Integer); + return schema; + } +}; + +} // namespace geckoprofiler::markers + +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; + } + + nonPIDBuf.print("%s: ", sevString); + if (aStr) { + nonPIDBuf.print("%s: ", aStr); + } + if (aExpr) { + nonPIDBuf.print("'%s', ", aExpr); + } + if (aFile || aLine != -1) { + nonPIDBuf.print("file %s:%d", aFile ? aFile : "<unknown>", + aLine != -1 ? aLine : 0); + } + + // Print "[PID]" or "[Desc PID]" at the beginning of the message. + buf.print("["); + if (sMultiprocessDescription) { + buf.print("%s ", sMultiprocessDescription); + } + + bool isMainthread = (NS_IsMainThreadTLSInitialized() && NS_IsMainThread()); + PRThread* currentThread = PR_GetCurrentThread(); + const char* currentThreadName = + isMainthread ? "Main Thread" : PR_GetThreadName(currentThread); + if (currentThreadName) { + buf.print("%" PRIPID ", %s] %s", base::GetCurrentProcId(), + currentThreadName, nonPIDBuf.buffer); + } else { + buf.print("%" PRIPID ", Unnamed thread %p] %s", base::GetCurrentProcId(), + currentThread, nonPIDBuf.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 + + PROFILER_MARKER("NS_DebugBreak", OTHER, MarkerStack::Capture(), + DebugBreakMarker, aSeverity, + ProfilerString8View::WrapNullTerminatedString(aStr), + ProfilerString8View::WrapNullTerminatedString(aExpr), + ProfilerString8View::WrapNullTerminatedString(aFile), aLine); + + // 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: { + // 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( + CrashReporter::Annotation::AbortMessage, + nsDependentCString(nonPIDBuf.buffer)); + } + +#if defined(DEBUG) && defined(_WIN32) + RealBreak(); +#endif +#if defined(DEBUG) + MozWalkTheStack(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: + MozWalkTheStack(stderr); + return; + + case NS_ASSERT_STACK_AND_ABORT: + MozWalkTheStack(stderr); + // Fall through to abort + [[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) { + NoteIntentionalCrash(XRE_GetProcessTypeString()); + MOZ_CRASH_UNSAFE(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(__aarch64__) + asm("brk #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__) || defined(__aarch64__) + RealBreak(); +#elif defined(SOLARIS) + RealBreak(); +#else +# warning do not know how to break on this platform +#endif +} + +nsresult nsDebugImpl::Create(const nsIID& aIID, void** aInstancePtr) { + static const nsDebugImpl* sImpl; + + 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) { + CrashReporter::AnnotateOOMAllocationSize(aSize); + MOZ_CRASH("OOM"); +} diff --git a/xpcom/base/nsDebugImpl.h b/xpcom/base/nsDebugImpl.h new file mode 100644 index 0000000000..a3adcad3d7 --- /dev/null +++ b/xpcom/base/nsDebugImpl.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 nsDebugImpl_h +#define nsDebugImpl_h + +#include "nsIDebug2.h" + +class nsDebugImpl : public nsIDebug2 { + public: + nsDebugImpl() = default; + NS_DECL_ISUPPORTS + NS_DECL_NSIDEBUG2 + + static nsresult Create(const nsIID& aIID, void** aInstancePtr); + + /* + * If we are in multiprocess mode, return the process name. + */ + static const char* GetMultiprocessMode(); + + /* + * 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 0000000000..c7bbbf4eb3 --- /dev/null +++ b/xpcom/base/nsDumpUtils.cpp @@ -0,0 +1,488 @@ +/* -*- 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 <errno.h> +#include "prenv.h" +#include "mozilla/Services.h" +#include "nsIObserverService.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/Unused.h" +#include "SpecialSystemDirectory.h" + +#ifdef XP_UNIX // { +# include "mozilla/Preferences.h" +# include <fcntl.h> +# include <unistd.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 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( + "FdWatcher::StartWatching", 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() = default; + +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("debug_info_trigger"_ns); + 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) { + if (NS_IsMainThread()) { + // This allows tests to override, but isn't safe off-mainthread. + rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, aFile); + } else { + rv = GetSpecialSystemDirectory(OS_TemporaryDirectory, 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.IsEmpty()) { + 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 0000000000..7aa9c6b735 --- /dev/null +++ b/xpcom/base/nsDumpUtils.h @@ -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/. */ + +#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( + "FdWatcher::StopWatching", 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. + */ + // The length of this array must match the size of the string constant in + // the definition in nsDumpUtils.cpp. A mismatch will result in a compile-time + // error. + static const char kPrefName[38]; + + static FifoWatcher* GetSingleton(); + + static bool MaybeCreate(); + + void RegisterCallback(const nsCString& aCommand, FifoCallback aCallback); + + virtual ~FifoWatcher(); + + virtual int OpenFd() override; + + virtual void OnFileCanReadWithoutBlocking(int aFd) override; + + private: + nsAutoCString mDirPath; + + static mozilla::StaticRefPtr<FifoWatcher> sSingleton; + + explicit FifoWatcher(nsCString aPath) + : mDirPath(aPath), mFifoInfoLock("FifoWatcher.mFifoInfoLock") {} + + mozilla::Mutex mFifoInfoLock; // protects mFifoInfo + FifoInfoArray mFifoInfo MOZ_GUARDED_BY(mFifoInfoLock); +}; + +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() override; + + virtual void StopWatching() override; + + virtual void OnFileCanReadWithoutBlocking(int aFd) override; + + private: + static mozilla::StaticRefPtr<SignalPipeWatcher> sSingleton; + + SignalPipeWatcher() : mSignalInfoLock("SignalPipeWatcher.mSignalInfoLock") { + MOZ_ASSERT(NS_IsMainThread()); + } + + mozilla::Mutex mSignalInfoLock; // protects mSignalInfo + SignalInfoArray mSignalInfo MOZ_GUARDED_BY(mSignalInfoLock); +}; + +#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 = ""_ns, + Mode aMode = CREATE_UNIQUE); +}; + +#endif diff --git a/xpcom/base/nsError.h b/xpcom/base/nsError.h new file mode 100644 index 0000000000..040cd4612c --- /dev/null +++ b/xpcom/base/nsError.h @@ -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/. */ + +#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> + +#define NS_ERROR_SEVERITY_SUCCESS 0 +#define NS_ERROR_SEVERITY_ERROR 1 + +#include "ErrorList.h" // IWYU pragma: export + +/** + * @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/nsGZFileWriter.cpp b/xpcom/base/nsGZFileWriter.cpp new file mode 100644 index 0000000000..2d8e14e944 --- /dev/null +++ b/xpcom/base/nsGZFileWriter.cpp @@ -0,0 +1,97 @@ +/* -*- 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 + +nsGZFileWriter::nsGZFileWriter(Operation aMode) + : mMode(aMode), mInitialized(false), mFinished(false), mGZFile(nullptr) {} + +nsGZFileWriter::~nsGZFileWriter() { + if (mInitialized && !mFinished) { + Finish(); + } +} + +nsresult 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); +} + +nsresult nsGZFileWriter::InitANSIFileDesc(FILE* aFile) { + if (NS_WARN_IF(mInitialized) || NS_WARN_IF(mFinished)) { + return NS_ERROR_FAILURE; + } + + 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; +} + +nsresult 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; +} + +nsresult 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 0000000000..dc33b4a2de --- /dev/null +++ b/xpcom/base/nsGZFileWriter.h @@ -0,0 +1,79 @@ +/* -*- 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 "nsISupportsImpl.h" +#include "zlib.h" +#include "nsDependentString.h" +#include <stdio.h> + +/** + * A simple class 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. + */ +class nsGZFileWriter final { + virtual ~nsGZFileWriter(); + + public: + enum Operation { Append, Create }; + + explicit nsGZFileWriter(Operation aMode = Create); + + NS_INLINE_DECL_REFCOUNTING(nsGZFileWriter) + + /** + * 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(). + */ + [[nodiscard]] nsresult Init(nsIFile* aFile); + + /** + * Alternate version of Init() for use when the file is already opened; + * e.g., with a FileDescriptor passed over IPC. + */ + [[nodiscard]] nsresult InitANSIFileDesc(FILE* aFile); + + /** + * Write the given string to the file. + */ + [[nodiscard]] nsresult Write(const nsACString& aStr); + + /** + * Write |length| bytes of |str| to the file. + */ + [[nodiscard]] nsresult Write(const char* aStr, uint32_t aLen) { + return Write(nsDependentCSubstring(aStr, aLen)); + } + + /** + * Close this nsGZFileWriter. This method will be run 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. + */ + nsresult Finish(); + + private: + Operation mMode; + bool mInitialized; + bool mFinished; + gzFile mGZFile; +}; + +#endif diff --git a/xpcom/base/nsIAvailableMemoryWatcherBase.idl b/xpcom/base/nsIAvailableMemoryWatcherBase.idl new file mode 100644 index 0000000000..07d48e8571 --- /dev/null +++ b/xpcom/base/nsIAvailableMemoryWatcherBase.idl @@ -0,0 +1,36 @@ +/* -*- 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" + +/** + * nsITabUnloader: interface to represent TabUnloader + * + * nsIAvailableMemoryWatcherBase: interface to watch the system's memory + * status and invoke a registered TabUnloader when it detected a low-memory + * and high-memory situation. The logic to detect such a memory situation + * is defined per platform. + */ + +[scriptable, uuid(2e530956-6054-464f-9f4c-0ae6f8de5523)] +interface nsITabUnloader : nsISupports +{ + /* + * Unload the least-recently-used tab. + * JS implementation of this interface TabUnloader.unloadTabAsync takes + * one parameter that defines a threshold to exclude fresh tabs from the + * unloading candidate tabs. Currently the memory watcher is the only one + * caller of this interface and it always expects the default threshold, + * so this interface takes no parameter. + */ + void unloadTabAsync(); +}; + +[scriptable, uuid(b0b5701e-239d-49db-9009-37e89f86441c)] +interface nsIAvailableMemoryWatcherBase : nsISupports +{ + void registerTabUnloader(in nsITabUnloader aTabUnloader); + void onUnloadAttemptCompleted(in nsresult aResult); +}; diff --git a/xpcom/base/nsIClassInfoImpl.h b/xpcom/base/nsIClassInfoImpl.h new file mode 100644 index 0000000000..045e75e5af --- /dev/null +++ b/xpcom/base/nsIClassInfoImpl.h @@ -0,0 +1,193 @@ +/* -*- 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 nsIClassInfoImpl_h__ +#define nsIClassInfoImpl_h__ + +#include "mozilla/Alignment.h" +#include "mozilla/Assertions.h" +#include "mozilla/MacroArgs.h" +#include "mozilla/MacroForEach.h" +#include "nsIClassInfo.h" +#include "nsISupportsImpl.h" + +#include <new> + +/** + * This header file provides macros which help you make your class implement + * nsIClassInfo. Implementing nsIClassInfo is particularly helpful if you have + * a C++ class which implements multiple interfaces and which you access from + * JavaScript. If that class implements nsIClassInfo, the JavaScript code + * won't have to call QueryInterface on instances of the class; all methods + * from all interfaces returned by GetInterfaces() will be available + * automagically. + * + * Here's all you need to do. Given a class + * + * class nsFooBar : public nsIFoo, public nsIBar { }; + * + * you should already have the following nsISupports implementation in its cpp + * file: + * + * NS_IMPL_ISUPPORTS(nsFooBar, nsIFoo, nsIBar). + * + * Change this to + * + * NS_IMPL_CLASSINFO(nsFooBar, nullptr, 0, NS_FOOBAR_CID) + * NS_IMPL_ISUPPORTS_CI(nsFooBar, nsIFoo, nsIBar) + * + * If nsFooBar is threadsafe, change the 0 above to nsIClassInfo::THREADSAFE. + * If it's a singleton, use nsIClassInfo::SINGLETON. The full list of flags is + * in nsIClassInfo.idl. + * + * The nullptr parameter is there so you can pass a function for converting + * from an XPCOM object to a scriptable helper. Unless you're doing + * specialized JS work, you can probably leave this as nullptr. + * + * This file also defines the NS_IMPL_QUERY_INTERFACE_CI macro, which you can + * use to replace NS_IMPL_QUERY_INTERFACE, if you use that instead of + * NS_IMPL_ISUPPORTS. + * + * That's it! The rest is gory details. + * + * + * Notice that nsFooBar didn't need to inherit from nsIClassInfo in order to + * "implement" it. However, after adding these macros to nsFooBar, you you can + * QueryInterface an instance of nsFooBar to nsIClassInfo. How can this be? + * + * The answer lies in the NS_IMPL_ISUPPORTS_CI macro. It modifies nsFooBar's + * QueryInterface implementation such that, if we ask to QI to nsIClassInfo, it + * returns a singleton object associated with the class. (That singleton is + * defined by NS_IMPL_CLASSINFO.) So all nsFooBar instances will return the + * same object when QI'ed to nsIClassInfo. (You can see this in + * NS_IMPL_QUERY_CLASSINFO below.) + * + * This hack breaks XPCOM's rules, since if you take an instance of nsFooBar, + * QI it to nsIClassInfo, and then try to QI to nsIFoo, that will fail. On the + * upside, implementing nsIClassInfo doesn't add a vtable pointer to instances + * of your class. + * + * In principal, you can also implement nsIClassInfo by inheriting from the + * interface. But some code expects that when it QI's an object to + * nsIClassInfo, it gets back a singleton which isn't attached to any + * particular object. If a class were to implement nsIClassInfo through + * inheritance, that code might QI to nsIClassInfo and keep the resulting + * object alive, thinking it was only keeping alive the classinfo singleton, + * but in fact keeping a whole instance of the class alive. See, e.g., bug + * 658632. + * + * Unless you specifically need to have a different nsIClassInfo instance for + * each instance of your class, you should probably just implement nsIClassInfo + * as a singleton. + */ + +class GenericClassInfo : public nsIClassInfo { + public: + struct ClassInfoData { + // This function pointer uses NS_CALLBACK_ because it's always set to an + // NS_IMETHOD function, which uses __stdcall on Win32. + typedef NS_CALLBACK_(nsresult, GetInterfacesProc)(nsTArray<nsIID>& aArray); + GetInterfacesProc getinterfaces; + + // This function pointer doesn't use NS_CALLBACK_ because it's always set to + // a vanilla function. + typedef nsresult (*GetScriptableHelperProc)(nsIXPCScriptable** aHelper); + GetScriptableHelperProc getscriptablehelper; + + uint32_t flags; + nsCID cid; + }; + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSICLASSINFO + + explicit GenericClassInfo(const ClassInfoData* aData) : mData(aData) {} + + private: + const ClassInfoData* mData; +}; + +#define NS_CLASSINFO_NAME(_class) g##_class##_classInfoGlobal +#define NS_CI_INTERFACE_GETTER_NAME(_class) _class##_GetInterfacesHelper +#define NS_DECL_CI_INTERFACE_GETTER(_class) \ + extern NS_IMETHODIMP NS_CI_INTERFACE_GETTER_NAME(_class)(nsTArray<nsIID> & \ + array); + +#define NS_IMPL_CLASSINFO(_class, _getscriptablehelper, _flags, _cid) \ + NS_DECL_CI_INTERFACE_GETTER(_class) \ + static const GenericClassInfo::ClassInfoData k##_class##ClassInfoData = { \ + NS_CI_INTERFACE_GETTER_NAME(_class), \ + _getscriptablehelper, \ + _flags | nsIClassInfo::SINGLETON_CLASSINFO, \ + _cid, \ + }; \ + mozilla::AlignedStorage2<GenericClassInfo> k##_class##ClassInfoDataPlace; \ + nsIClassInfo* NS_CLASSINFO_NAME(_class) = nullptr; + +#define NS_IMPL_QUERY_CLASSINFO(_class) \ + if (aIID.Equals(NS_GET_IID(nsIClassInfo))) { \ + if (!NS_CLASSINFO_NAME(_class)) \ + NS_CLASSINFO_NAME(_class) = new (k##_class##ClassInfoDataPlace.addr()) \ + GenericClassInfo(&k##_class##ClassInfoData); \ + foundInterface = NS_CLASSINFO_NAME(_class); \ + } else + +#define NS_CLASSINFO_HELPER_BEGIN(_class, _c) \ + NS_IMETHODIMP \ + NS_CI_INTERFACE_GETTER_NAME(_class)(nsTArray<nsIID> & array) { \ + array.Clear(); \ + array.SetCapacity(_c); + +#define NS_CLASSINFO_HELPER_ENTRY(_interface) \ + array.AppendElement(NS_GET_IID(_interface)); + +#define NS_CLASSINFO_HELPER_END \ + return NS_OK; \ + } + +#define NS_IMPL_CI_INTERFACE_GETTER(aClass, ...) \ + static_assert(MOZ_ARG_COUNT(__VA_ARGS__) > 0, \ + "Need more arguments to NS_IMPL_CI_INTERFACE_GETTER"); \ + NS_CLASSINFO_HELPER_BEGIN(aClass, MOZ_ARG_COUNT(__VA_ARGS__)) \ + MOZ_FOR_EACH(NS_CLASSINFO_HELPER_ENTRY, (), (__VA_ARGS__)) \ + NS_CLASSINFO_HELPER_END + +#define NS_IMPL_CI_INTERFACE_GETTER0(aClass) \ + NS_CLASSINFO_HELPER_BEGIN(aClass, 0) \ + NS_CLASSINFO_HELPER_END + +// Note that this macro is an internal implementation of this header and +// should not be used outside it. It does not end the interface map as this +// is done in NS_IMPL_QUERY_INTERFACE_CI or the _INHERITED variant. +#define NS_IMPL_QUERY_INTERFACE_CI_GUTS(aClass, ...) \ + static_assert(MOZ_ARG_COUNT(__VA_ARGS__) > 0, \ + "Need more arguments to NS_IMPL_QUERY_INTERFACE_CI"); \ + NS_INTERFACE_MAP_BEGIN(aClass) \ + MOZ_FOR_EACH(NS_INTERFACE_MAP_ENTRY, (), (__VA_ARGS__)) \ + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, MOZ_ARG_1(__VA_ARGS__)) \ + NS_IMPL_QUERY_CLASSINFO(aClass) + +#define NS_IMPL_QUERY_INTERFACE_CI(aClass, ...) \ + NS_IMPL_QUERY_INTERFACE_CI_GUTS(aClass, __VA_ARGS__) \ + NS_INTERFACE_MAP_END + +#define NS_IMPL_QUERY_INTERFACE_CI_INHERITED(aClass, aSuper, ...) \ + NS_IMPL_QUERY_INTERFACE_CI_GUTS(aClass, __VA_ARGS__) \ + NS_INTERFACE_MAP_END_INHERITING \ + (aSuper) + +#define NS_IMPL_QUERY_INTERFACE_CI_INHERITED0(aClass, aSuper) \ + NS_INTERFACE_MAP_BEGIN(aClass) \ + NS_IMPL_QUERY_CLASSINFO(aClass) \ + NS_INTERFACE_MAP_END_INHERITING(aSuper) + +#define NS_IMPL_ISUPPORTS_CI(aClass, ...) \ + NS_IMPL_ADDREF(aClass) \ + NS_IMPL_RELEASE(aClass) \ + NS_IMPL_QUERY_INTERFACE_CI(aClass, __VA_ARGS__) \ + NS_IMPL_CI_INTERFACE_GETTER(aClass, __VA_ARGS__) + +#endif // nsIClassInfoImpl_h__ diff --git a/xpcom/base/nsIConsoleListener.idl b/xpcom/base/nsIConsoleListener.idl new file mode 100644 index 0000000000..45e6fcb1fc --- /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 0000000000..ec9b901aaa --- /dev/null +++ b/xpcom/base/nsIConsoleMessage.idl @@ -0,0 +1,52 @@ +/* -*- 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; + + /** + * The time (in microseconds from the Epoch) that the message instance + * was initialised. + * The timestamp is initialized as JS_now. + */ + readonly attribute long long microSecondTimeStamp; + + [binaryname(MessageMoz)] readonly attribute AString message; + + attribute boolean isForwardedFromContentProcess; + + 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 0000000000..0d20b678d0 --- /dev/null +++ b/xpcom/base/nsIConsoleService.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" + +interface nsIConsoleListener; +interface nsIConsoleMessage; + +[scriptable, builtinclass, uuid(0eb81d20-c37e-42d4-82a8-ca9ae96bdf52)] +interface nsIConsoleService : nsISupports +{ + void logMessage(in nsIConsoleMessage message); + + // This helper function executes `func` and redirects any exception + // that may be thrown while running it to the DevTools Console currently + // debugging `targetGlobal`. + // + // This helps flag the nsIScriptError with a particular innerWindowID + // which is especially useful for WebExtension content scripts + // where script are running in a Sandbox whose prototype is the content window. + // We expect content script exception to be flaged with the content window + // innerWindowID in order to appear in the tab's DevTools. + [implicit_jscontext] + jsval callFunctionAndLogException(in jsval targetGlobal, in jsval func); + + // 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. + cenum OutputMode : 8 { + SuppressLog = 0, + OutputToLog + }; + void logMessageWithMode(in nsIConsoleMessage message, + in nsIConsoleService_OutputMode mode); + + /** + * Convenience method for logging simple messages. + */ + void logStringMessage(in wstring message); + + /** + * Get an array of all the messages logged so far. + */ + Array<nsIConsoleMessage> getMessageArray(); + + /** + * 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(); + + /** + * Clear the message buffer for a given window. + */ + void resetWindow(in uint64_t windowInnerId); +}; + + +%{ 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 0000000000..5e0b059a13 --- /dev/null +++ b/xpcom/base/nsICycleCollectorListener.idl @@ -0,0 +1,166 @@ +/* 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 nsICycleCollectorListener 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 an nsICycleCollectorListener + * 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 nsICycleCollectorListener, which implements this + * interface. In C++, this can be done by calling + * nsCycleCollector_createLogger(). In JS, this can be done by calling + * Components.utils.createCCLogger(). + * + * - 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/nsID.cpp b/xpcom/base/nsID.cpp new file mode 100644 index 0000000000..701bf647d1 --- /dev/null +++ b/xpcom/base/nsID.cpp @@ -0,0 +1,239 @@ +/* -*- 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 "nsID.h" + +#include <limits.h> + +#include "MainThreadUtils.h" +#include "mozilla/Assertions.h" +#include "mozilla/RandomNum.h" +#include "mozilla/Sprintf.h" +#include "nss.h" +#include "ScopedNSSTypes.h" + +[[nodiscard]] static bool GenerateRandomBytesFromNSS(void* aBuffer, + size_t aLength) { + MOZ_ASSERT(aBuffer); + + // Bounds check that we can safely truncate size_t `aLength` to an int. + if (aLength == 0 || aLength > INT_MAX) { + MOZ_ASSERT_UNREACHABLE("Bad aLength"); + return false; + } + int len = static_cast<int>(aLength); + + // Only try to use NSS on the main thread. + if (!NS_IsMainThread() || !NSS_IsInitialized()) { + return false; + } + + mozilla::UniquePK11SlotInfo slot(PK11_GetInternalSlot()); + if (!slot) { + MOZ_ASSERT_UNREACHABLE("Null slot"); + return false; + } + + SECStatus srv = PK11_GenerateRandomOnSlot( + slot.get(), static_cast<unsigned char*>(aBuffer), len); + MOZ_ASSERT(srv == SECSuccess); + return (srv == SECSuccess); +} + +nsresult nsID::GenerateUUIDInPlace(nsID& aId) { + // Firefox needs to generate some UUIDs before NSS has been initialized. We + // prefer NSS's RNG, but if NSS is not available yet or returns an error, fall + // back to MFBT's GenerateRandomBytes(). + if (!GenerateRandomBytesFromNSS(&aId, sizeof(nsID)) && + !mozilla::GenerateRandomBytesFromOS(&aId, sizeof(nsID))) { + MOZ_ASSERT_UNREACHABLE("GenerateRandomBytesFromOS() failed"); + return NS_ERROR_NOT_AVAILABLE; + } + + // Put in the version + aId.m2 &= 0x0fff; + aId.m2 |= 0x4000; + + // Put in the variant + aId.m3[0] &= 0x3f; + aId.m3[0] |= 0x80; + + return NS_OK; +} + +nsID nsID::GenerateUUID() { + nsID uuid; + nsresult rv = GenerateUUIDInPlace(uuid); + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv)); + return uuid; +} + +void nsID::Clear() { + m0 = 0; + m1 = 0; + m2 = 0; + memset(m3, 0, sizeof(m3)); +} + +/** + * Multiplies the_int_var with 16 (0x10) and adds the value of the + * hexadecimal digit the_char. If it fails it returns false from + * the function it's used in. + */ + +#define ADD_HEX_CHAR_TO_INT_OR_RETURN_FALSE(the_char, the_int_var) \ + the_int_var = (the_int_var << 4) + the_char; \ + if (the_char >= '0' && the_char <= '9') \ + the_int_var -= '0'; \ + else if (the_char >= 'a' && the_char <= 'f') \ + the_int_var -= 'a' - 10; \ + else if (the_char >= 'A' && the_char <= 'F') \ + the_int_var -= 'A' - 10; \ + else \ + return false + +/** + * Parses number_of_chars characters from the char_pointer pointer and + * puts the number in the dest_variable. The pointer is moved to point + * at the first character after the parsed ones. If it fails it returns + * false from the function the macro is used in. + */ + +#define PARSE_CHARS_TO_NUM(char_pointer, dest_variable, number_of_chars) \ + do { \ + int32_t _i = number_of_chars; \ + dest_variable = 0; \ + while (_i) { \ + ADD_HEX_CHAR_TO_INT_OR_RETURN_FALSE(*char_pointer, dest_variable); \ + char_pointer++; \ + _i--; \ + } \ + } while (0) + +/** + * Parses a hyphen from the char_pointer string. If there is no hyphen there + * the function returns false from the function it's used in. The + * char_pointer is advanced one step. + */ + +#define PARSE_HYPHEN(char_pointer) \ + if (*(char_pointer++) != '-') return false + +/* + * Turns a {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} string into + * an nsID. It can also handle the old format without the { and }. + */ + +bool nsID::Parse(const char* aIDStr) { + /* Optimized for speed */ + if (!aIDStr) { + return false; + } + + bool expectFormat1 = (aIDStr[0] == '{'); + if (expectFormat1) { + ++aIDStr; + } + + PARSE_CHARS_TO_NUM(aIDStr, m0, 8); + PARSE_HYPHEN(aIDStr); + PARSE_CHARS_TO_NUM(aIDStr, m1, 4); + PARSE_HYPHEN(aIDStr); + PARSE_CHARS_TO_NUM(aIDStr, m2, 4); + PARSE_HYPHEN(aIDStr); + int i; + for (i = 0; i < 2; ++i) { + PARSE_CHARS_TO_NUM(aIDStr, m3[i], 2); + } + PARSE_HYPHEN(aIDStr); + while (i < 8) { + PARSE_CHARS_TO_NUM(aIDStr, m3[i], 2); + i++; + } + + return expectFormat1 ? *aIDStr == '}' : true; +} + +#ifndef XPCOM_GLUE_AVOID_NSPR + +/* + * Returns a managed string in {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} + * format. + */ +nsIDToCString nsID::ToString() const { return nsIDToCString(*this); } + +static const char sHexChars[256 * 2 + 1] = + "000102030405060708090a0b0c0d0e0f" + "101112131415161718191a1b1c1d1e1f" + "202122232425262728292a2b2c2d2e2f" + "303132333435363738393a3b3c3d3e3f" + "404142434445464748494a4b4c4d4e4f" + "505152535455565758595a5b5c5d5e5f" + "606162636465666768696a6b6c6d6e6f" + "707172737475767778797a7b7c7d7e7f" + "808182838485868788898a8b8c8d8e8f" + "909192939495969798999a9b9c9d9e9f" + "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf" + "b0b1b2b3b4b5b6b7b8b9babbbcbdbebf" + "c0c1c2c3c4c5c6c7c8c9cacbcccdcecf" + "d0d1d2d3d4d5d6d7d8d9dadbdcdddedf" + "e0e1e2e3e4e5e6e7e8e9eaebecedeeef" + "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"; + +// Writes a zero-padded 8-bit integer as lowercase hex into aDest[0..2] +static void ToHex8Bit(uint8_t aValue, char* aDest) { + aDest[0] = sHexChars[2 * aValue]; + aDest[1] = sHexChars[2 * aValue + 1]; +} + +// Writes a zero-padded 16-bit integer as lowercase hex into aDest[0..4] +static void ToHex16Bit(uint16_t aValue, char* aDest) { + const uint8_t hi = (aValue >> 8); + const uint8_t lo = aValue; + ToHex8Bit(hi, &aDest[0]); + ToHex8Bit(lo, &aDest[2]); +} + +// Writes a zero-padded 32-bit integer as lowercase hex into aDest[0..8] +static void ToHex32Bit(uint32_t aValue, char* aDest) { + const uint16_t hi = (aValue >> 16); + const uint16_t lo = aValue; + ToHex16Bit(hi, &aDest[0]); + ToHex16Bit(lo, &aDest[4]); +} + +void nsID::ToProvidedString(char (&aDest)[NSID_LENGTH]) const { + // Stringify manually, for best performance. + // + // "{%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}" + // "{ecc35fd2-9029-4287-a826-b0a241d48d31}" + aDest[0] = '{'; + ToHex32Bit(m0, &aDest[1]); + aDest[9] = '-'; + ToHex16Bit(m1, &aDest[10]); + aDest[14] = '-'; + ToHex16Bit(m2, &aDest[15]); + aDest[19] = '-'; + ToHex8Bit(m3[0], &aDest[20]); + ToHex8Bit(m3[1], &aDest[22]); + aDest[24] = '-'; + ToHex8Bit(m3[2], &aDest[25]); + ToHex8Bit(m3[3], &aDest[27]); + ToHex8Bit(m3[4], &aDest[29]); + ToHex8Bit(m3[5], &aDest[31]); + ToHex8Bit(m3[6], &aDest[33]); + ToHex8Bit(m3[7], &aDest[35]); + aDest[37] = '}'; + aDest[38] = '\0'; +} + +#endif // XPCOM_GLUE_AVOID_NSPR + +nsID* nsID::Clone() const { + auto id = static_cast<nsID*>(moz_xmalloc(sizeof(nsID))); + *id = *this; + return id; +} diff --git a/xpcom/base/nsID.h b/xpcom/base/nsID.h new file mode 100644 index 0000000000..c7feddd1cc --- /dev/null +++ b/xpcom/base/nsID.h @@ -0,0 +1,173 @@ +/* -*- 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 nsID_h__ +#define nsID_h__ + +#include <string.h> + +#include "nscore.h" + +#define NSID_LENGTH 39 + +#ifndef XPCOM_GLUE_AVOID_NSPR +class nsIDToCString; +#endif + +/** + * A "unique identifier". This is modeled after OSF DCE UUIDs. + */ + +struct nsID { + uint32_t m0; + uint16_t m1; + uint16_t m2; + uint8_t m3[8]; + + /** + * Create a new random UUID. + * GenerateUUIDInPlace() is fallible, whereas GenerateUUID() will abort in + * the unlikely case that the OS RNG returns an error. + */ + [[nodiscard]] static nsresult GenerateUUIDInPlace(nsID& aId); + static nsID GenerateUUID(); + + /** + * Ensures everything is zeroed out. + */ + void Clear(); + + /** + * Equivalency method. Compares this nsID with another. + * @return true if they are the same, false if not. + */ + + inline bool Equals(const nsID& aOther) const { + // At the time of this writing, modern compilers (namely Clang) inline this + // memcmp call into a single SIMD operation on x86/x86-64 architectures (as + // long as we compare to zero rather than returning the full integer return + // value), which is measurably more efficient to any manual comparisons we + // can do directly. +#if defined(__x86_64__) || defined(__i386__) + return !memcmp(this, &aOther, sizeof *this); +#else + // However, on ARM architectures, compilers still tend to generate a direct + // memcmp call, which we'd like to avoid. + return (((uint32_t*)&m0)[0] == ((uint32_t*)&aOther.m0)[0]) && + (((uint32_t*)&m0)[1] == ((uint32_t*)&aOther.m0)[1]) && + (((uint32_t*)&m0)[2] == ((uint32_t*)&aOther.m0)[2]) && + (((uint32_t*)&m0)[3] == ((uint32_t*)&aOther.m0)[3]); +#endif + } + + inline bool operator==(const nsID& aOther) const { return Equals(aOther); } + + /** + * nsID Parsing method. Turns a {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} + * string into an nsID + */ + bool Parse(const char* aIDStr); + +#ifndef XPCOM_GLUE_AVOID_NSPR + /** + * nsID string encoder. Returns a managed string in + * {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} format. + */ + nsIDToCString ToString() const; + + /** + * nsID string encoder. Builds a string in + * {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} format, into a char[NSID_LENGTH] + * buffer provided by the caller (for instance, on the stack). + */ + void ToProvidedString(char (&aDest)[NSID_LENGTH]) const; + +#endif // XPCOM_GLUE_AVOID_NSPR + + // Infallibly duplicate an nsID. Must be freed with free(). + nsID* Clone() const; +}; + +#ifndef XPCOM_GLUE_AVOID_NSPR +/** + * A stack helper class to convert a nsID to a string. Useful + * for printing nsIDs. For example: + * nsID aID = ...; + * printf("%s", nsIDToCString(aID).get()); + */ +class nsIDToCString { + public: + explicit nsIDToCString(const nsID& aID) { + aID.ToProvidedString(mStringBytes); + } + + const char* get() const { return mStringBytes; } + + protected: + char mStringBytes[NSID_LENGTH]; +}; +#endif + +/* + * Class IDs + */ + +typedef nsID nsCID; + +// Define an CID +#define NS_DEFINE_CID(_name, _cidspec) const nsCID _name = _cidspec + +#define NS_DEFINE_NAMED_CID(_name) static const nsCID k##_name = _name + +#define REFNSCID const nsCID& + +/** + * An "interface id" which can be used to uniquely identify a given + * interface. + */ + +typedef nsID nsIID; + +/** + * A macro shorthand for <tt>const nsIID&<tt> + */ + +#define REFNSIID const nsIID& + +/** + * A macro to build the static const IID accessor method. The Dummy + * template parameter only exists so that the kIID symbol will be linked + * properly (weak symbol on linux, gnu_linkonce on mac, multiple-definitions + * merged on windows). Dummy should always be instantiated as "void". + */ + +#define NS_DECLARE_STATIC_IID_ACCESSOR(the_iid) \ + template <typename T, typename U> \ + struct COMTypeInfo; + +#define NS_DEFINE_STATIC_IID_ACCESSOR(the_interface, the_iid) \ + template <typename T> \ + struct the_interface::COMTypeInfo<the_interface, T> { \ + static const nsIID kIID NS_HIDDEN; \ + }; \ + template <typename T> \ + const nsIID the_interface::COMTypeInfo<the_interface, T>::kIID NS_HIDDEN = \ + the_iid; + +/** + * A macro to build the static const CID accessor method + */ + +#define NS_DEFINE_STATIC_CID_ACCESSOR(the_cid) \ + static const nsID& GetCID() { \ + static const nsID cid = the_cid; \ + return cid; \ + } + +#define NS_GET_IID(T) (T::COMTypeInfo<T, void>::kIID) +#define NS_GET_TEMPLATE_IID(T) (T::template COMTypeInfo<T, void>::kIID) + +#endif diff --git a/xpcom/base/nsIDUtils.h b/xpcom/base/nsIDUtils.h new file mode 100644 index 0000000000..ffb9d02f19 --- /dev/null +++ b/xpcom/base/nsIDUtils.h @@ -0,0 +1,32 @@ +/* -*- 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 XPCOM_BASE_NSIDUTILS_H_ +#define XPCOM_BASE_NSIDUTILS_H_ + +#include "nsID.h" +#include "nsString.h" +#include "nsTString.h" + +/** + * Trims the brackets around the nsID string, since it wraps its ID with + * brackets: {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}. + */ +template <typename T> +class NSID_TrimBrackets : public nsTAutoString<T> { + public: + explicit NSID_TrimBrackets(const nsID& aID) { + nsIDToCString toCString(aID); + // The string from nsIDToCString::get() includes a null terminator. + // Thus this trims 3 characters: `{`, `}`, and the null terminator. + this->AssignASCII(toCString.get() + 1, NSID_LENGTH - 3); + } +}; + +using NSID_TrimBracketsUTF16 = NSID_TrimBrackets<char16_t>; +using NSID_TrimBracketsASCII = NSID_TrimBrackets<char>; + +#endif // XPCOM_BASE_NSIDUTILS_H_ diff --git a/xpcom/base/nsIDebug2.idl b/xpcom/base/nsIDebug2.idl new file mode 100644 index 0000000000..4672fea7ea --- /dev/null +++ b/xpcom/base/nsIDebug2.idl @@ -0,0 +1,100 @@ +/* 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" + +/** + * @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. + * + */ + +[builtinclass, 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); + + /** + * Request the process to trigger a fatal panic!() from Rust code. + * + * @param aMessage the string to pass to panic!(). + */ + void rustPanic(in string aMessage); + + /** + * Request the process to log a message for a target and level from Rust code. + * + * @param aTarget the string representing the log target. + * @param aMessage the string representing the log message. + */ + void rustLog(in string aTarget, + in string aMessage); + + /** + * Cause an Out of Memory Crash. + */ + void crashWithOOM(); +}; diff --git a/xpcom/base/nsIException.idl b/xpcom/base/nsIException.idl new file mode 100644 index 0000000000..7f529e1c08 --- /dev/null +++ b/xpcom/base/nsIException.idl @@ -0,0 +1,80 @@ +/* -*- 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" + +[ptr] native JSContext(JSContext); +native StackFrameRef(already_AddRefed<nsIStackFrame>); + +[scriptable, builtinclass, uuid(28bfb2a2-5ea6-4738-918b-049dc4d51f0b)] +interface nsIStackFrame : nsISupports +{ + [implicit_jscontext, binaryname(FilenameXPCOM)] + readonly attribute AString filename; + [implicit_jscontext, binaryname(NameXPCOM)] + readonly attribute AString name; + // Unique identifier of the script source for the frame, or zero. + [implicit_jscontext, binaryname(SourceIdXPCOM)] + readonly attribute int32_t sourceId; + // Valid line numbers begin at '1'. '0' indicates unknown. + [implicit_jscontext, binaryname(LineNumberXPCOM)] + readonly attribute int32_t lineNumber; + [implicit_jscontext, binaryname(ColumnNumberXPCOM)] + readonly attribute int32_t columnNumber; + readonly attribute AUTF8String sourceLine; + [implicit_jscontext, binaryname(AsyncCauseXPCOM)] + readonly attribute AString asyncCause; + [implicit_jscontext, binaryname(AsyncCallerXPCOM)] + readonly attribute nsIStackFrame asyncCaller; + [implicit_jscontext, binaryname(CallerXPCOM)] + 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, binaryname(FormattedStackXPCOM)] + 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, binaryname(ToStringXPCOM)] + AUTF8String toString(); + + // Infallible things to be called from C++. + [notxpcom, nostdcall] + void getFilename(in JSContext aCx, out AString aFilename); + [notxpcom, nostdcall] + void getName(in JSContext aCx, out AString aName); + [notxpcom, nostdcall] + int32_t getSourceId(in JSContext aCx); + [notxpcom, nostdcall] + int32_t getLineNumber(in JSContext aCx); + [notxpcom, nostdcall] + int32_t getColumnNumber(in JSContext aCx); + [notxpcom, nostdcall] + void getAsyncCause(in JSContext aCx, out AString aAsyncCause); + [notxpcom, nostdcall] + StackFrameRef getAsyncCaller(in JSContext aCx); + [notxpcom, nostdcall] + StackFrameRef getCaller(in JSContext aCx); + [notxpcom, nostdcall] + void getFormattedStack(in JSContext aCx, out AString aFormattedStack); + [notxpcom, nostdcall, binaryname(ToString)] + void toStringInfallible(in JSContext aCx, out AUTF8String aString); +}; + +// This interface only exists because of all the JS consumers doing +// "instanceof Ci.nsIException". We should switch them to something else and +// get rid of it; bug 1435856 tracks that. C++ code should NOT use this; use +// mozilla::dom::Exception instead. +[scriptable, builtinclass, uuid(4371b5bf-6845-487f-8d9d-3f1e4a9badd2)] +interface nsIException : nsISupports +{ +}; diff --git a/xpcom/base/nsIInterfaceRequestor.idl b/xpcom/base/nsIInterfaceRequestor.idl new file mode 100644 index 0000000000..e0c4c0d7bb --- /dev/null +++ b/xpcom/base/nsIInterfaceRequestor.idl @@ -0,0 +1,35 @@ +/* -*- 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/nsIInterfaceRequestorUtils.cpp b/xpcom/base/nsIInterfaceRequestorUtils.cpp new file mode 100644 index 0000000000..44dd51921d --- /dev/null +++ b/xpcom/base/nsIInterfaceRequestorUtils.cpp @@ -0,0 +1,32 @@ +/* -*- 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 "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" + +nsresult nsGetInterface::operator()(const nsIID& aIID, + void** aInstancePtr) const { + nsresult status; + + if (mSource) { + nsCOMPtr<nsIInterfaceRequestor> factoryPtr = do_QueryInterface(mSource); + if (factoryPtr) { + status = factoryPtr->GetInterface(aIID, aInstancePtr); + } else { + status = NS_ERROR_NO_INTERFACE; + } + } else { + status = NS_ERROR_NULL_POINTER; + } + + if (NS_FAILED(status)) { + *aInstancePtr = 0; + } + if (mErrorPtr) { + *mErrorPtr = status; + } + return status; +} diff --git a/xpcom/base/nsIInterfaceRequestorUtils.h b/xpcom/base/nsIInterfaceRequestorUtils.h new file mode 100644 index 0000000000..c3116ea9f2 --- /dev/null +++ b/xpcom/base/nsIInterfaceRequestorUtils.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 __nsInterfaceRequestorUtils_h +#define __nsInterfaceRequestorUtils_h + +#include "nsCOMPtr.h" + +// a type-safe shortcut for calling the |GetInterface()| member function +// T must inherit from nsIInterfaceRequestor, but the cast may be ambiguous. +template <class T, class DestinationType> +inline nsresult CallGetInterface(T* aSource, DestinationType** aDestination) { + MOZ_ASSERT(aSource, "null parameter"); + MOZ_ASSERT(aDestination, "null parameter"); + + return aSource->GetInterface(NS_GET_TEMPLATE_IID(DestinationType), + reinterpret_cast<void**>(aDestination)); +} + +class MOZ_STACK_CLASS nsGetInterface final : public nsCOMPtr_helper { + public: + nsGetInterface(nsISupports* aSource, nsresult* aError) + : mSource(aSource), mErrorPtr(aError) {} + + virtual nsresult NS_FASTCALL operator()(const nsIID&, void**) const override; + + private: + nsISupports* MOZ_NON_OWNING_REF mSource; + nsresult* mErrorPtr; +}; + +inline const nsGetInterface do_GetInterface(nsISupports* aSource, + nsresult* aError = 0) { + return nsGetInterface(aSource, aError); +} + +#endif // __nsInterfaceRequestorUtils_h diff --git a/xpcom/base/nsIMacPreferencesReader.idl b/xpcom/base/nsIMacPreferencesReader.idl new file mode 100644 index 0000000000..470fd92ec9 --- /dev/null +++ b/xpcom/base/nsIMacPreferencesReader.idl @@ -0,0 +1,34 @@ +/* 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 "nsISupportsPrimitives.idl" + +%{C++ +#define ENTERPRISE_POLICIES_ENABLED_KEY "EnterprisePoliciesEnabled" +%} + +/** + * This interface is designed to provide scriptable access to the macOS + * preferences system. + * + * This interface is highly macOS specific. + */ +[builtinclass, scriptable, uuid(b0f20595-88ce-4738-a1a4-24de78eb8051)] +interface nsIMacPreferencesReader : nsISupports +{ + /** + * This method checks whether macOS policies are enabled. + * + * @return true if macOS policies are enabled, false otherwise. + */ + bool policiesEnabled(); + + /** + * This method reads and returns the macOS preferences. + * + * @return A JSON object containing all macOS preferences. + */ + [implicit_jscontext] + jsval readPreferences(); +}; diff --git a/xpcom/base/nsIMemoryInfoDumper.idl b/xpcom/base/nsIMemoryInfoDumper.idl new file mode 100644 index 0000000000..79b03a709d --- /dev/null +++ b/xpcom/base/nsIMemoryInfoDumper.idl @@ -0,0 +1,167 @@ +/* -*- 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? + * + * @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. + * + * 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, + in boolean aMinimizeMemoryUsage); + + /** + * 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 0000000000..05b5701519 --- /dev/null +++ b/xpcom/base/nsIMemoryReporter.idl @@ -0,0 +1,615 @@ +/* -*- 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 nsIHandleReportCallback : 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 nsIHandleReportCallback callback, + in nsISupports data, + in boolean anonymize); + + /* + * Kinds. See the |kind| comment in nsIHandleReportCallback. + */ + const int32_t KIND_NONHEAP = 0; + const int32_t KIND_HEAP = 1; + const int32_t KIND_OTHER = 2; + + /* + * Units. See the |units| comment in nsIHandleReportCallback. + */ + 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, function, uuid(1a80cd0f-0d9e-4397-be69-68ad28fe5175)] +interface nsIHeapAllocatedCallback : nsISupports +{ + void callback(in int64_t bytesAllocated); +}; + +[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 nsIHandleReportCallback 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 nsIHandleReportCallback 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 nsIHandleReportCallback 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. + * + * |JSMainRuntimeRealms{System,User}| (UNITS_COUNT) The number of + * {system,user} realms in the main JS runtime. + * + * |imagesContentUsedUncompressed| (UNITS_BYTES) Memory used for decoded + * raster images in content. + * + * |storageSQLite| (UNITS_BYTES) Memory used by SQLite. + * + * |lowMemoryEventsPhysical| (UNITS_COUNT_CUMULATIVE) + * The number of low-physical-memory events that have occurred since the + * process started. + * + * |ghostWindows| (UNITS_COUNT) A cached value of the number of ghost + * windows. This should have been updated within the past 60s. + * + * |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 JSMainRuntimeRealmsSystem; + [must_use] readonly attribute int64_t JSMainRuntimeRealmsUser; + + [must_use] readonly attribute int64_t imagesContentUsedUncompressed; + + [must_use] readonly attribute int64_t storageSQLite; + + [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 doing this for the current process, we invoke + * the given runnable if it's not null. We do not wait for any child processes + * that might be doing their own minimization via child-mmu-request to finish. + */ + [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 "nsString.h" +#include "nsTArray.h" + +class nsPIDOMWindowOuter; + +namespace mozilla { + +// All the following registration/unregistration functions don't use +// [[nodiscard]] 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, JSMainRuntimeRealmsSystem) +DECL_REGISTER_DISTINGUISHED_AMOUNT(Infallible, JSMainRuntimeRealmsUser) + +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, LowMemoryEventsPhysical) + +DECL_REGISTER_DISTINGUISHED_AMOUNT(Infallible, GhostWindows) + +#undef DECL_REGISTER_DISTINGUISHED_AMOUNT +#undef DECL_UNREGISTER_DISTINGUISHED_AMOUNT + +// Likewise for per-tab measurement. + +typedef nsresult (*JSSizeOfTabFn)(JSObject* aObj, + size_t* aJsObjectsSize, + size_t* aJsStringSize, + size_t* aJsPrivateSize, + size_t* aJsOtherSize); +typedef 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); \ + } + +// This is an alternative to MOZ_DEFINE_MALLOC_SIZE_OF that defines a +// MallocSizeOf function that can handle interior pointers. +#ifdef MOZ_MEMORY + +#include "mozmemory.h" + +#define MOZ_DEFINE_MALLOC_ENCLOSING_SIZE_OF(fn) \ + static size_t fn(const void* aPtr) \ + { \ + jemalloc_ptr_info_t info; \ + jemalloc_ptr_info(aPtr, &info); \ + MOZ_REPORT(info.addr); \ + return jemalloc_ptr_is_live(&info) ? info.size : 0; \ + } + +#else + +#define MOZ_DEFINE_MALLOC_ENCLOSING_SIZE_OF(fn) \ + static size_t fn(const void* aPtr) \ + { \ + return 0; \ + } + +#endif + +// 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(""_ns, nsLiteralCString(path), \ + kind, units, amount, \ + nsLiteralCString(description), aData) + +%} diff --git a/xpcom/base/nsINIParser.cpp b/xpcom/base/nsINIParser.cpp new file mode 100644 index 0000000000..1447aa6fbf --- /dev/null +++ b/xpcom/base/nsINIParser.cpp @@ -0,0 +1,325 @@ +/* -*- 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/. */ + +// Moz headers (alphabetical) +#include "nsCRTGlue.h" +#include "nsError.h" +#include "nsIFile.h" +#include "nsINIParser.h" +#include "mozilla/ResultExtensions.h" +#include "mozilla/Try.h" +#include "mozilla/URLPreloader.h" + +using namespace mozilla; + +nsresult nsINIParser::Init(nsIFile* aFile) { + nsCString result; + MOZ_TRY_VAR(result, URLPreloader::ReadFile(aFile)); + + return InitFromString(result); +} + +static const char kNL[] = "\r\n"; +static const char kEquals[] = "="; +static const char kWhitespace[] = " \t"; +static const char kRBracket[] = "]"; + +nsresult nsINIParser::InitFromString(const nsCString& aStr) { + nsCString fileContents; + char* buffer; + + if (StringHead(aStr, 3) == "\xEF\xBB\xBF") { + // Someone set us up the Utf-8 BOM + // This case is easy, since we assume that BOM-less + // files are Utf-8 anyway. Just skip the BOM and process as usual. + fileContents.Append(aStr); + buffer = fileContents.BeginWriting() + 3; + } else { + if (StringHead(aStr, 2) == "\xFF\xFE") { + // Someone set us up the Utf-16LE BOM + nsDependentSubstring str(reinterpret_cast<const char16_t*>(aStr.get()), + aStr.Length() / 2); + + AppendUTF16toUTF8(Substring(str, 1), fileContents); + } else { + fileContents.Append(aStr); + } + + buffer = fileContents.BeginWriting(); + } + + char* currSection = nullptr; + + // outer loop tokenizes into lines + while (char* token = NS_strtok(kNL, &buffer)) { + if (token[0] == '#' || token[0] == ';') { // it's a comment + continue; + } + + token = (char*)NS_strspnp(kWhitespace, token); + if (!*token) { // empty line + continue; + } + + if (token[0] == '[') { // section header! + ++token; + currSection = token; + + char* rb = NS_strtok(kRBracket, &token); + if (!rb || NS_strtok(kWhitespace, &token)) { + // there's either an unclosed [Section or a [Section]Moretext! + // we could frankly decide that this INI file is malformed right + // here and stop, but we won't... keep going, looking for + // a well-formed [section] to continue working with + currSection = nullptr; + } + + continue; + } + + if (!currSection) { + // If we haven't found a section header (or we found a malformed + // section header), don't bother parsing this line. + continue; + } + + char* key = token; + char* e = NS_strtok(kEquals, &token); + if (!e || !token) { + continue; + } + + SetString(currSection, key, token); + } + + return NS_OK; +} + +bool nsINIParser::IsValidSection(const char* aSection) { + if (aSection[0] == '\0') { + return false; + } + + const char* found = strpbrk(aSection, "\r\n[]"); + return found == nullptr; +} + +bool nsINIParser::IsValidKey(const char* aKey) { + if (aKey[0] == '\0') { + return false; + } + + const char* found = strpbrk(aKey, "\r\n="); + return found == nullptr; +} + +bool nsINIParser::IsValidValue(const char* aValue) { + const char* found = strpbrk(aValue, "\r\n"); + return found == nullptr; +} + +nsresult nsINIParser::GetString(const char* aSection, const char* aKey, + nsACString& aResult) { + if (!IsValidSection(aSection) || !IsValidKey(aKey)) { + return NS_ERROR_INVALID_ARG; + } + + INIValue* val; + mSections.Get(aSection, &val); + + while (val) { + if (strcmp(val->key, aKey) == 0) { + aResult.Assign(val->value); + return NS_OK; + } + + val = val->next.get(); + } + + return NS_ERROR_FAILURE; +} + +nsresult nsINIParser::GetString(const char* aSection, const char* aKey, + char* aResult, uint32_t aResultLen) { + if (!IsValidSection(aSection) || !IsValidKey(aKey)) { + return NS_ERROR_INVALID_ARG; + } + + INIValue* val; + mSections.Get(aSection, &val); + + while (val) { + if (strcmp(val->key, aKey) == 0) { + strncpy(aResult, val->value, aResultLen); + aResult[aResultLen - 1] = '\0'; + if (strlen(val->value) >= aResultLen) { + return NS_ERROR_LOSS_OF_SIGNIFICANT_DATA; + } + + return NS_OK; + } + + val = val->next.get(); + } + + return NS_ERROR_FAILURE; +} + +nsresult nsINIParser::GetSections(INISectionCallback aCB, void* aClosure) { + for (const auto& key : mSections.Keys()) { + if (!aCB(key, aClosure)) { + break; + } + } + return NS_OK; +} + +nsresult nsINIParser::GetStrings(const char* aSection, INIStringCallback aCB, + void* aClosure) { + if (!IsValidSection(aSection)) { + return NS_ERROR_INVALID_ARG; + } + + INIValue* val; + + for (mSections.Get(aSection, &val); val; val = val->next.get()) { + if (!aCB(val->key, val->value, aClosure)) { + return NS_OK; + } + } + + return NS_OK; +} + +nsresult nsINIParser::SetString(const char* aSection, const char* aKey, + const char* aValue) { + if (!IsValidSection(aSection) || !IsValidKey(aKey) || !IsValidValue(aValue)) { + return NS_ERROR_INVALID_ARG; + } + + mSections.WithEntryHandle(aSection, [&](auto&& entry) { + if (!entry) { + entry.Insert(MakeUnique<INIValue>(aKey, aValue)); + return; + } + + INIValue* v = entry->get(); + + // Check whether this key has already been specified; overwrite + // if so, or append if not. + while (v) { + if (!strcmp(aKey, v->key)) { + v->SetValue(aValue); + break; + } + if (!v->next) { + v->next = MakeUnique<INIValue>(aKey, aValue); + break; + } + v = v->next.get(); + } + NS_ASSERTION(v, "v should never be null coming out of this loop"); + }); + + return NS_OK; +} + +nsresult nsINIParser::DeleteString(const char* aSection, const char* aKey) { + if (!IsValidSection(aSection) || !IsValidKey(aKey)) { + return NS_ERROR_INVALID_ARG; + } + + INIValue* val; + if (!mSections.Get(aSection, &val)) { + return NS_ERROR_FAILURE; + } + + // Special case the first result + if (strcmp(val->key, aKey) == 0) { + if (!val->next) { + mSections.Remove(aSection); + } else { + mSections.InsertOrUpdate(aSection, std::move(val->next)); + delete val; + } + return NS_OK; + } + + while (val->next) { + if (strcmp(val->next->key, aKey) == 0) { + val->next = std::move(val->next->next); + + return NS_OK; + } + + val = val->next.get(); + } + + return NS_ERROR_FAILURE; +} + +nsresult nsINIParser::DeleteSection(const char* aSection) { + if (!IsValidSection(aSection)) { + return NS_ERROR_INVALID_ARG; + } + + if (!mSections.Remove(aSection)) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +nsresult nsINIParser::RenameSection(const char* aSection, + const char* aNewName) { + if (!IsValidSection(aSection) || !IsValidSection(aNewName)) { + return NS_ERROR_INVALID_ARG; + } + + if (mSections.Contains(aNewName)) { + return NS_ERROR_ILLEGAL_VALUE; + } + + mozilla::UniquePtr<INIValue> val; + if (mSections.Remove(aSection, &val)) { + mSections.InsertOrUpdate(aNewName, std::move(val)); + } else { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +nsresult nsINIParser::WriteToFile(nsIFile* aFile) { + nsCString buffer; + + WriteToString(buffer); + + FILE* writeFile; + nsresult rv = aFile->OpenANSIFileDesc("w", &writeFile); + NS_ENSURE_SUCCESS(rv, rv); + + unsigned int length = buffer.Length(); + + if (fwrite(buffer.get(), sizeof(char), length, writeFile) != length) { + fclose(writeFile); + return NS_ERROR_UNEXPECTED; + } + + fclose(writeFile); + return NS_OK; +} + +void nsINIParser::WriteToString(nsACString& aOutput) { + for (const auto& entry : mSections) { + aOutput.AppendPrintf("[%s]\n", entry.GetKey()); + INIValue* val = entry.GetWeak(); + while (val) { + aOutput.AppendPrintf("%s=%s\n", val->key, val->value); + val = val->next.get(); + } + aOutput.AppendLiteral("\n"); + } +} diff --git a/xpcom/base/nsINIParser.h b/xpcom/base/nsINIParser.h new file mode 100644 index 0000000000..3b26a0d511 --- /dev/null +++ b/xpcom/base/nsINIParser.h @@ -0,0 +1,168 @@ +/* -*- 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 was shamelessly copied from mozilla/xpinstall/wizard/unix/src2 + +#ifndef nsINIParser_h__ +#define nsINIParser_h__ + +#ifdef MOZILLA_INTERNAL_API +# define nsINIParser nsINIParser_internal +#endif + +#include "nscore.h" +#include "nsClassHashtable.h" +#include "mozilla/UniquePtr.h" + +#include <stdio.h> + +class nsIFile; + +class nsINIParser { + public: + nsINIParser() {} + ~nsINIParser() = default; + + /** + * Initialize the INIParser with a nsIFile. If this method fails, no + * other methods should be called. This method reads and parses the file, + * the class does not hold a file handle open. An instance must only be + * initialized once. + */ + nsresult Init(nsIFile* aFile); + + nsresult InitFromString(const nsCString& aStr); + + /** + * Callback for GetSections + * @return false to stop enumeration, or true to continue. + */ + typedef bool (*INISectionCallback)(const char* aSection, void* aClosure); + + /** + * Enumerate the sections within the INI file. + */ + nsresult GetSections(INISectionCallback aCB, void* aClosure); + + /** + * Callback for GetStrings + * @return false to stop enumeration, or true to continue + */ + typedef bool (*INIStringCallback)(const char* aString, const char* aValue, + void* aClosure); + + /** + * Enumerate the strings within a section. If the section does + * not exist, this function will silently return. + */ + nsresult GetStrings(const char* aSection, INIStringCallback aCB, + void* aClosure); + + /** + * Get the value of the specified key in the specified section + * of the INI file represented by this instance. + * + * @param aSection section name + * @param aKey key name + * @param aResult the value found + * @throws NS_ERROR_FAILURE if the specified section/key could not be + * found. + */ + nsresult GetString(const char* aSection, const char* aKey, + nsACString& aResult); + + /** + * Alternate signature of GetString that uses a pre-allocated buffer + * instead of a nsACString (for use in the standalone glue before + * the glue is initialized). + * + * @throws NS_ERROR_LOSS_OF_SIGNIFICANT_DATA if the aResult buffer is not + * large enough for the data. aResult will be filled with as + * much data as possible. + * + * @see GetString [1] + */ + nsresult GetString(const char* aSection, const char* aKey, char* aResult, + uint32_t aResultLen); + + /** + * Sets the value of the specified key in the specified section. The section + * is created if it does not already exist. + * + * @oaram aSection section name + * @param aKey key name + * @param aValue the value to set + */ + nsresult SetString(const char* aSection, const char* aKey, + const char* aValue); + + /** + * Deletes the value of the specified key in the specified section. + * + * @param aSection section name + * @param aKey key name + * + * @throws NS_ERROR_FAILURE if the string was not set. + */ + nsresult DeleteString(const char* aSection, const char* aKey); + + /** + * Deletes the specified section. + * + * @param aSection section name + * + * @throws NS_ERROR_FAILURE if the section did not exist. + */ + nsresult DeleteSection(const char* aSection); + + /** + * Renames the specified section. + * + * @param aSection section name + * @param aNewName new section name + * + * @throws NS_ERROR_FAILURE if the section did not exist. + * @throws NS_ERROR_ILLEGAL_VALUE if the new section name already exists. + */ + nsresult RenameSection(const char* aSection, const char* aNewName); + + /** + * Writes the ini data to disk. + * @param aFile the file to write to + * @throws NS_ERROR_FAILURE on failure. + */ + nsresult WriteToFile(nsIFile* aFile); + + void WriteToString(nsACString& aOutput); + + private: + struct INIValue { + INIValue(const char* aKey, const char* aValue) + : key(strdup(aKey)), value(strdup(aValue)) {} + + ~INIValue() { + delete key; + delete value; + } + + void SetValue(const char* aValue) { + delete value; + value = strdup(aValue); + } + + const char* key; + const char* value; + mozilla::UniquePtr<INIValue> next; + }; + + nsClassHashtable<nsCharPtrHashKey, INIValue> mSections; + + bool IsValidSection(const char* aSection); + bool IsValidKey(const char* aKey); + bool IsValidValue(const char* aValue); +}; + +#endif /* nsINIParser_h__ */ diff --git a/xpcom/base/nsISecurityConsoleMessage.idl b/xpcom/base/nsISecurityConsoleMessage.idl new file mode 100644 index 0000000000..917968d83b --- /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 0000000000..7d0ef4d74f --- /dev/null +++ b/xpcom/base/nsISizeOf.h @@ -0,0 +1,39 @@ +/* -*- 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/nsISupports.idl b/xpcom/base/nsISupports.idl new file mode 100644 index 0000000000..45c6c5d589 --- /dev/null +++ b/xpcom/base/nsISupports.idl @@ -0,0 +1,60 @@ +/* -*- 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. + */ + +#include "nsrootidl.idl" + +/** + * 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. + * + * Historically, nsISupports needed to be binary compatible with COM's + * IUnknown, so the IID of nsISupports is the same as it. That is no + * longer a goal, and hopefully nobody depends on it. We may break + * this compatibility at any time. + */ +[scriptable, uuid(00000000-0000-0000-c000-000000000046)] +interface nsISupports { + + /** + * 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. + */ + void QueryInterface(in nsIIDRef aIID, + [iid_is(aIID), retval] out nsQIResult aInstancePtr); + + /** + * 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. + */ + [noscript, notxpcom] MozExternalRefCountType AddRef(); + + /** + * 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. + */ + [noscript, notxpcom] MozExternalRefCountType Release(); +}; + +%{C++ +#include "nsISupportsUtils.h" +%} diff --git a/xpcom/base/nsISupportsImpl.cpp b/xpcom/base/nsISupportsImpl.cpp new file mode 100644 index 0000000000..c282ec14db --- /dev/null +++ b/xpcom/base/nsISupportsImpl.cpp @@ -0,0 +1,144 @@ +/* -*- 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 "nsISupportsImpl.h" + +#include "mozilla/Assertions.h" +#ifndef XPCOM_GLUE_AVOID_NSPR +# include "nsPrintfCString.h" +# include "nsThreadUtils.h" +#endif + +using namespace mozilla; + +nsresult NS_FASTCALL NS_TableDrivenQI(void* aThis, REFNSIID aIID, + void** aInstancePtr, + const QITableEntry* aEntries) { + do { + if (aIID.Equals(*aEntries->iid)) { + nsISupports* r = reinterpret_cast<nsISupports*>( + reinterpret_cast<char*>(aThis) + aEntries->offset); + NS_ADDREF(r); + *aInstancePtr = r; + return NS_OK; + } + + ++aEntries; + } while (aEntries->iid); + + *aInstancePtr = nullptr; + return NS_ERROR_NO_INTERFACE; +} + +#ifndef XPCOM_GLUE_AVOID_NSPR +# ifdef MOZ_THREAD_SAFETY_OWNERSHIP_CHECKS_SUPPORTED +nsAutoOwningThread::nsAutoOwningThread() : mThread(PR_GetCurrentThread()) {} + +void nsAutoOwningThread::AssertCurrentThreadOwnsMe(const char* msg) const { + if (MOZ_UNLIKELY(!IsCurrentThread())) { + // `msg` is a string literal by construction. + MOZ_CRASH_UNSAFE(msg); + } +} + +bool nsAutoOwningThread::IsCurrentThread() const { + return mThread == PR_GetCurrentThread(); +} + +nsAutoOwningEventTarget::nsAutoOwningEventTarget() + : mTarget(GetCurrentSerialEventTarget()) { + mTarget->AddRef(); +} + +nsAutoOwningEventTarget::~nsAutoOwningEventTarget() { + nsCOMPtr<nsISerialEventTarget> target = dont_AddRef(mTarget); +} + +void nsAutoOwningEventTarget ::AssertCurrentThreadOwnsMe( + const char* msg) const { + if (MOZ_UNLIKELY(!IsCurrentThread())) { + // `msg` is a string literal by construction. + MOZ_CRASH_UNSAFE(msg); + } +} + +bool nsAutoOwningEventTarget::IsCurrentThread() const { + return mTarget->IsOnCurrentThread(); +} + +# endif + +namespace mozilla::detail { +class ProxyDeleteVoidRunnable final : public CancelableRunnable { + public: + ProxyDeleteVoidRunnable(const char* aName, void* aPtr, + DeleteVoidFunction* aDeleteFunc) + : CancelableRunnable(aName), mPtr(aPtr), mDeleteFunc(aDeleteFunc) {} + + NS_IMETHOD Run() override { + if (mPtr) { + mDeleteFunc(mPtr); + mPtr = nullptr; + } + return NS_OK; + } + + // Mimics the behaviour in `ProxyRelease`, freeing the resource when + // cancelled. + nsresult Cancel() override { return Run(); } + +# ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY + NS_IMETHOD GetName(nsACString& aName) override { + if (mName) { + aName.Append(mName); + } else { + aName.AssignLiteral("ProxyDeleteVoidRunnable"); + } + return NS_OK; + } +# endif + + private: + ~ProxyDeleteVoidRunnable() { + if (mPtr) { +# ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY + NS_WARNING( + nsPrintfCString( + "ProxyDeleteVoidRunnable for '%s' never run, leaking!", mName) + .get()); +# else + NS_WARNING("ProxyDeleteVoidRunnable never run, leaking!"); +# endif + } + } + + void* mPtr; + DeleteVoidFunction* mDeleteFunc; +}; + +void ProxyDeleteVoid(const char* aName, nsISerialEventTarget* aTarget, + void* aPtr, DeleteVoidFunction* aDeleteFunc) { + MOZ_ASSERT(aName); + MOZ_ASSERT(aPtr); + MOZ_ASSERT(aDeleteFunc); + if (!aTarget) { + NS_WARNING(nsPrintfCString("no target for '%s', leaking!", aName).get()); + return; + } + + if (aTarget->IsOnCurrentThread()) { + aDeleteFunc(aPtr); + return; + } + nsresult rv = aTarget->Dispatch( + MakeAndAddRef<ProxyDeleteVoidRunnable>(aName, aPtr, aDeleteFunc), + NS_DISPATCH_NORMAL); + if (NS_FAILED(rv)) { + NS_WARNING(nsPrintfCString("failed to post '%s', leaking!", aName).get()); + } +} +} // namespace mozilla::detail +#endif diff --git a/xpcom/base/nsISupportsImpl.h b/xpcom/base/nsISupportsImpl.h new file mode 100644 index 0000000000..27ff85b385 --- /dev/null +++ b/xpcom/base/nsISupportsImpl.h @@ -0,0 +1,1484 @@ +/* -*- 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 nsISupportsImpl_h__ +#define nsISupportsImpl_h__ + +#include "nscore.h" +#include "nsISupports.h" +#include "nsISupportsUtils.h" + +#if !defined(XPCOM_GLUE_AVOID_NSPR) +# include "prthread.h" /* needed for cargo-culting headers */ +#endif + +#include "nsDebug.h" +#include "nsXPCOM.h" +#include <atomic> +#include <type_traits> +#include "mozilla/Attributes.h" +#include "mozilla/Assertions.h" +#include "mozilla/Atomics.h" +#include "mozilla/Compiler.h" +#include "mozilla/Likely.h" +#include "mozilla/MacroArgs.h" +#include "mozilla/MacroForEach.h" + +#define MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(X) \ + static_assert(!std::is_destructible_v<X>, \ + "Reference-counted class " #X \ + " should not have a public destructor. " \ + "Make this class's destructor non-public"); + +inline nsISupports* ToSupports(decltype(nullptr)) { return nullptr; } + +inline nsISupports* ToSupports(nsISupports* aSupports) { return aSupports; } + +//////////////////////////////////////////////////////////////////////////////// +// Macros to help detect thread-safety: + +#ifdef MOZ_THREAD_SAFETY_OWNERSHIP_CHECKS_SUPPORTED + +# include "prthread.h" /* needed for thread-safety checks */ + +class nsAutoOwningThread { + public: + nsAutoOwningThread(); + + // We move the actual assertion checks out-of-line to minimize code bloat, + // but that means we have to pass a non-literal string to MOZ_CRASH_UNSAFE. + // To make that more safe, the public interface requires a literal string + // and passes that to the private interface; we can then be assured that we + // effectively are passing a literal string to MOZ_CRASH_UNSAFE. + template <int N> + void AssertOwnership(const char (&aMsg)[N]) const { + AssertCurrentThreadOwnsMe(aMsg); + } + + bool IsCurrentThread() const; + + private: + void AssertCurrentThreadOwnsMe(const char* aMsg) const; + + void* mThread; +}; + +class nsISerialEventTarget; +class nsAutoOwningEventTarget { + public: + nsAutoOwningEventTarget(); + ~nsAutoOwningEventTarget(); + + // We move the actual assertion checks out-of-line to minimize code bloat, + // but that means we have to pass a non-literal string to MOZ_CRASH_UNSAFE. + // To make that more safe, the public interface requires a literal string + // and passes that to the private interface; we can then be assured that we + // effectively are passing a literal string to MOZ_CRASH_UNSAFE. + template <int N> + void AssertOwnership(const char (&aMsg)[N]) const { + AssertCurrentThreadOwnsMe(aMsg); + } + + bool IsCurrentThread() const; + + private: + void AssertCurrentThreadOwnsMe(const char* aMsg) const; + + nsISerialEventTarget* mTarget; +}; + +# define NS_DECL_OWNINGTHREAD nsAutoOwningThread _mOwningThread; +# define NS_DECL_OWNINGEVENTTARGET nsAutoOwningEventTarget _mOwningThread; +# define NS_ASSERT_OWNINGTHREAD(_class) \ + _mOwningThread.AssertOwnership(#_class " not thread-safe") +#else // !MOZ_THREAD_SAFETY_OWNERSHIP_CHECKS_SUPPORTED + +# define NS_DECL_OWNINGTHREAD /* nothing */ +# define NS_DECL_OWNINGEVENTTARGET /* nothing */ +# define NS_ASSERT_OWNINGTHREAD(_class) ((void)0) + +#endif // MOZ_THREAD_SAFETY_OWNERSHIP_CHECKS_SUPPORTED + +// Macros for reference-count and constructor logging + +#if defined(NS_BUILD_REFCNT_LOGGING) + +# define NS_LOG_ADDREF(_p, _rc, _type, _size) \ + NS_LogAddRef((_p), (_rc), (_type), (uint32_t)(_size)) + +# define NS_LOG_RELEASE(_p, _rc, _type) NS_LogRelease((_p), (_rc), (_type)) + +# define MOZ_ASSERT_CLASSNAME(_type) \ + static_assert(std::is_class_v<_type>, \ + "Token '" #_type "' is not a class type.") + +# define MOZ_ASSERT_NOT_ISUPPORTS(_type) \ + static_assert(!std::is_base_of<nsISupports, _type>::value, \ + "nsISupports classes don't need to call MOZ_COUNT_CTOR or " \ + "MOZ_COUNT_DTOR"); + +// Note that the following constructor/destructor logging macros are redundant +// for refcounted objects that log via the NS_LOG_ADDREF/NS_LOG_RELEASE macros. +// Refcount logging is preferred. +# define MOZ_COUNT_CTOR(_type) \ + do { \ + MOZ_ASSERT_CLASSNAME(_type); \ + MOZ_ASSERT_NOT_ISUPPORTS(_type); \ + NS_LogCtor((void*)this, #_type, sizeof(*this)); \ + } while (0) + +# define MOZ_COUNT_CTOR_INHERITED(_type, _base) \ + do { \ + MOZ_ASSERT_CLASSNAME(_type); \ + MOZ_ASSERT_CLASSNAME(_base); \ + MOZ_ASSERT_NOT_ISUPPORTS(_type); \ + NS_LogCtor((void*)this, #_type, sizeof(*this) - sizeof(_base)); \ + } while (0) + +# define MOZ_LOG_CTOR(_ptr, _name, _size) \ + do { \ + NS_LogCtor((void*)_ptr, _name, _size); \ + } while (0) + +# define MOZ_COUNT_DTOR(_type) \ + do { \ + MOZ_ASSERT_CLASSNAME(_type); \ + MOZ_ASSERT_NOT_ISUPPORTS(_type); \ + NS_LogDtor((void*)this, #_type, sizeof(*this)); \ + } while (0) + +# define MOZ_COUNT_DTOR_INHERITED(_type, _base) \ + do { \ + MOZ_ASSERT_CLASSNAME(_type); \ + MOZ_ASSERT_CLASSNAME(_base); \ + MOZ_ASSERT_NOT_ISUPPORTS(_type); \ + NS_LogDtor((void*)this, #_type, sizeof(*this) - sizeof(_base)); \ + } while (0) + +# define MOZ_LOG_DTOR(_ptr, _name, _size) \ + do { \ + NS_LogDtor((void*)_ptr, _name, _size); \ + } while (0) + +# define MOZ_COUNTED_DEFAULT_CTOR(_type) \ + _type() { MOZ_COUNT_CTOR(_type); } + +# define MOZ_COUNTED_DTOR_META(_type, _prefix, _postfix) \ + _prefix ~_type() _postfix { MOZ_COUNT_DTOR(_type); } +# define MOZ_COUNTED_DTOR_NESTED(_type, _nestedName) \ + ~_type() { MOZ_COUNT_DTOR(_nestedName); } + +/* nsCOMPtr.h allows these macros to be defined by clients + * These logging functions require dynamic_cast<void*>, so they don't + * do anything useful if we don't have dynamic_cast<void*>. + * Note: The explicit comparison to nullptr is needed to avoid warnings + * when _p is a nullptr itself. */ +# define NSCAP_LOG_ASSIGNMENT(_c, _p) \ + if (_p != nullptr) NS_LogCOMPtrAddRef((_c), ToSupports(_p)) + +# define NSCAP_LOG_RELEASE(_c, _p) \ + if (_p) NS_LogCOMPtrRelease((_c), ToSupports(_p)) + +#else /* !NS_BUILD_REFCNT_LOGGING */ + +# define NS_LOG_ADDREF(_p, _rc, _type, _size) +# define NS_LOG_RELEASE(_p, _rc, _type) +# define MOZ_COUNT_CTOR(_type) +# define MOZ_COUNT_CTOR_INHERITED(_type, _base) +# define MOZ_LOG_CTOR(_ptr, _name, _size) +# define MOZ_COUNT_DTOR(_type) +# define MOZ_COUNT_DTOR_INHERITED(_type, _base) +# define MOZ_LOG_DTOR(_ptr, _name, _size) +# define MOZ_COUNTED_DEFAULT_CTOR(_type) _type() = default; +# define MOZ_COUNTED_DTOR_META(_type, _prefix, _postfix) \ + _prefix ~_type() _postfix = default; +# define MOZ_COUNTED_DTOR_NESTED(_type, _nestedName) ~_type() = default; + +#endif /* NS_BUILD_REFCNT_LOGGING */ + +#define MOZ_COUNTED_DTOR(_type) MOZ_COUNTED_DTOR_META(_type, , ) +#define MOZ_COUNTED_DTOR_OVERRIDE(_type) \ + MOZ_COUNTED_DTOR_META(_type, , override) +#define MOZ_COUNTED_DTOR_FINAL(_type) MOZ_COUNTED_DTOR_META(_type, , final) +#define MOZ_COUNTED_DTOR_VIRTUAL(_type) MOZ_COUNTED_DTOR_META(_type, virtual, ) + +// Support for ISupports classes which interact with cycle collector. + +#ifdef HAVE_64BIT_BUILD +# define NS_NUMBER_OF_FLAGS_IN_REFCNT 3 +# define NS_IS_ON_MAINTHREAD (1 << 2) +#else +# define NS_NUMBER_OF_FLAGS_IN_REFCNT 2 +#endif + +#define NS_IN_PURPLE_BUFFER (1 << 0) +#define NS_IS_PURPLE (1 << 1) +#define NS_REFCOUNT_CHANGE (1 << NS_NUMBER_OF_FLAGS_IN_REFCNT) +#define NS_REFCOUNT_VALUE(_val) (_val >> NS_NUMBER_OF_FLAGS_IN_REFCNT) + +class nsCycleCollectingAutoRefCnt { + public: + typedef void (*Suspect)(void* aPtr, nsCycleCollectionParticipant* aCp, + nsCycleCollectingAutoRefCnt* aRefCnt, + bool* aShouldDelete); + + nsCycleCollectingAutoRefCnt() : mRefCntAndFlags(0) {} + + explicit nsCycleCollectingAutoRefCnt(uintptr_t aValue) + : mRefCntAndFlags(aValue << NS_NUMBER_OF_FLAGS_IN_REFCNT) {} + + nsCycleCollectingAutoRefCnt(const nsCycleCollectingAutoRefCnt&) = delete; + void operator=(const nsCycleCollectingAutoRefCnt&) = delete; + + MOZ_ALWAYS_INLINE uintptr_t incr(nsISupports* aOwner) { + return incr(aOwner, nullptr); + } + + MOZ_ALWAYS_INLINE uintptr_t incr(void* aOwner, + nsCycleCollectionParticipant* aCp) { + mRefCntAndFlags += NS_REFCOUNT_CHANGE; + mRefCntAndFlags &= ~NS_IS_PURPLE; + // For incremental cycle collection, use the purple buffer to track objects + // that have been AddRef'd. + if (!IsInPurpleBuffer()) { + mRefCntAndFlags |= NS_IN_PURPLE_BUFFER; + // Refcount isn't zero, so Suspect won't delete anything. + MOZ_ASSERT(get() > 0); + NS_CycleCollectorSuspect3(aOwner, aCp, this, nullptr); + } + return NS_REFCOUNT_VALUE(mRefCntAndFlags); + } + + MOZ_ALWAYS_INLINE void stabilizeForDeletion() { + // Set refcnt to 1 and mark us to be in the purple buffer. + // This way decr won't call suspect again. + mRefCntAndFlags = NS_REFCOUNT_CHANGE | NS_IN_PURPLE_BUFFER; + } + + MOZ_ALWAYS_INLINE uintptr_t decr(nsISupports* aOwner, + bool* aShouldDelete = nullptr) { + return decr(aOwner, nullptr, aShouldDelete); + } + + MOZ_ALWAYS_INLINE uintptr_t decr(void* aOwner, + nsCycleCollectionParticipant* aCp, + bool* aShouldDelete = nullptr) { + MOZ_ASSERT(get() > 0); + if (!IsInPurpleBuffer()) { + mRefCntAndFlags -= NS_REFCOUNT_CHANGE; + mRefCntAndFlags |= (NS_IN_PURPLE_BUFFER | NS_IS_PURPLE); + uintptr_t retval = NS_REFCOUNT_VALUE(mRefCntAndFlags); + // Suspect may delete 'aOwner' and 'this'! + NS_CycleCollectorSuspect3(aOwner, aCp, this, aShouldDelete); + return retval; + } + mRefCntAndFlags -= NS_REFCOUNT_CHANGE; + mRefCntAndFlags |= (NS_IN_PURPLE_BUFFER | NS_IS_PURPLE); + return NS_REFCOUNT_VALUE(mRefCntAndFlags); + } + + MOZ_ALWAYS_INLINE void RemovePurple() { + MOZ_ASSERT(IsPurple(), "must be purple"); + mRefCntAndFlags &= ~NS_IS_PURPLE; + } + + MOZ_ALWAYS_INLINE void RemoveFromPurpleBuffer() { + MOZ_ASSERT(IsInPurpleBuffer()); + mRefCntAndFlags &= ~(NS_IS_PURPLE | NS_IN_PURPLE_BUFFER); + } + + MOZ_ALWAYS_INLINE bool IsPurple() const { + return !!(mRefCntAndFlags & NS_IS_PURPLE); + } + + MOZ_ALWAYS_INLINE bool IsInPurpleBuffer() const { + return !!(mRefCntAndFlags & NS_IN_PURPLE_BUFFER); + } + + // Cycle collected objects can only be used on a single thread, so if we ever + // see an object on the main thread, it will always be on the main thread. + // The cycle collector then uses this information to utilize faster nursery + // purple buffer on the main thread. + // The method works only on 64 bit systems. + MOZ_ALWAYS_INLINE void SetIsOnMainThread() { +#ifdef HAVE_64BIT_BUILD + mRefCntAndFlags |= NS_IS_ON_MAINTHREAD; +#endif + } + +#ifdef HAVE_64BIT_BUILD + // The NS_IS_ON_MAINTHREAD flag only exists on 64-bit builds. + MOZ_ALWAYS_INLINE bool IsOnMainThread() { + return !!(mRefCntAndFlags & NS_IS_ON_MAINTHREAD); + } +#endif + + MOZ_ALWAYS_INLINE nsrefcnt get() const { + return NS_REFCOUNT_VALUE(mRefCntAndFlags); + } + + MOZ_ALWAYS_INLINE operator nsrefcnt() const { return get(); } + + private: + uintptr_t mRefCntAndFlags; +}; + +class nsAutoRefCnt { + public: + nsAutoRefCnt() : mValue(0) {} + explicit nsAutoRefCnt(nsrefcnt aValue) : mValue(aValue) {} + + nsAutoRefCnt(const nsAutoRefCnt&) = delete; + void operator=(const nsAutoRefCnt&) = delete; + + // only support prefix increment/decrement + nsrefcnt operator++() { return ++mValue; } + nsrefcnt operator--() { return --mValue; } + + nsrefcnt operator=(nsrefcnt aValue) { return (mValue = aValue); } + operator nsrefcnt() const { return mValue; } + nsrefcnt get() const { return mValue; } + + static const bool isThreadSafe = false; + + private: + nsrefcnt operator++(int) = delete; + nsrefcnt operator--(int) = delete; + nsrefcnt mValue; +}; + +namespace mozilla { +class ThreadSafeAutoRefCnt { + public: + ThreadSafeAutoRefCnt() : mValue(0) {} + explicit ThreadSafeAutoRefCnt(nsrefcnt aValue) : mValue(aValue) {} + + ThreadSafeAutoRefCnt(const ThreadSafeAutoRefCnt&) = delete; + void operator=(const ThreadSafeAutoRefCnt&) = delete; + + // only support prefix increment/decrement + MOZ_ALWAYS_INLINE nsrefcnt operator++() { + // Memory synchronization is not required when incrementing a + // reference count. The first increment of a reference count on a + // thread is not important, since the first use of the object on a + // thread can happen before it. What is important is the transfer + // of the pointer to that thread, which may happen prior to the + // first increment on that thread. The necessary memory + // synchronization is done by the mechanism that transfers the + // pointer between threads. + return mValue.fetch_add(1, std::memory_order_relaxed) + 1; + } + MOZ_ALWAYS_INLINE nsrefcnt operator--() { + // Since this may be the last release on this thread, we need + // release semantics so that prior writes on this thread are visible + // to the thread that destroys the object when it reads mValue with + // acquire semantics. + nsrefcnt result = mValue.fetch_sub(1, std::memory_order_release) - 1; + if (result == 0) { + // We're going to destroy the object on this thread, so we need + // acquire semantics to synchronize with the memory released by + // the last release on other threads, that is, to ensure that + // writes prior to that release are now visible on this thread. +#ifdef MOZ_TSAN + // TSan doesn't understand std::atomic_thread_fence, so in order + // to avoid a false positive for every time a refcounted object + // is deleted, we replace the fence with an atomic operation. + mValue.load(std::memory_order_acquire); +#else + std::atomic_thread_fence(std::memory_order_acquire); +#endif + } + return result; + } + + MOZ_ALWAYS_INLINE nsrefcnt operator=(nsrefcnt aValue) { + // Use release semantics since we're not sure what the caller is + // doing. + mValue.store(aValue, std::memory_order_release); + return aValue; + } + MOZ_ALWAYS_INLINE operator nsrefcnt() const { return get(); } + MOZ_ALWAYS_INLINE nsrefcnt get() const { + // Use acquire semantics since we're not sure what the caller is + // doing. + return mValue.load(std::memory_order_acquire); + } + + static const bool isThreadSafe = true; + + private: + nsrefcnt operator++(int) = delete; + nsrefcnt operator--(int) = delete; + std::atomic<nsrefcnt> mValue; +}; + +namespace detail { + +// Type trait indicating whether a given XPCOM interface class may only be +// implemented by types with threadsafe refcounts. This is specialized for +// classes with the `rust_sync` annotation within XPIDL-generated header files, +// and checked within macro-generated QueryInterface implementations. +template <typename T> +class InterfaceNeedsThreadSafeRefCnt : public std::false_type {}; + +} +} // namespace mozilla + +/////////////////////////////////////////////////////////////////////////////// + +/** + * Declare the reference count variable and the implementations of the + * AddRef and QueryInterface methods. + */ + +#define NS_DECL_ISUPPORTS \ + public: \ + NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override; \ + NS_IMETHOD_(MozExternalRefCountType) AddRef(void) override; \ + NS_IMETHOD_(MozExternalRefCountType) Release(void) override; \ + using HasThreadSafeRefCnt = std::false_type; \ + \ + protected: \ + nsAutoRefCnt mRefCnt; \ + NS_DECL_OWNINGTHREAD \ + public: + +#define NS_DECL_ISUPPORTS_ONEVENTTARGET \ + public: \ + NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override; \ + NS_IMETHOD_(MozExternalRefCountType) AddRef(void) override; \ + NS_IMETHOD_(MozExternalRefCountType) Release(void) override; \ + using HasThreadSafeRefCnt = std::false_type; \ + \ + protected: \ + nsAutoRefCnt mRefCnt; \ + NS_DECL_OWNINGEVENTTARGET \ + public: + +#define NS_DECL_THREADSAFE_ISUPPORTS \ + public: \ + NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override; \ + NS_IMETHOD_(MozExternalRefCountType) AddRef(void) override; \ + NS_IMETHOD_(MozExternalRefCountType) Release(void) override; \ + using HasThreadSafeRefCnt = std::true_type; \ + \ + protected: \ + ::mozilla::ThreadSafeAutoRefCnt mRefCnt; \ + NS_DECL_OWNINGTHREAD \ + public: + +#define NS_DECL_CYCLE_COLLECTING_ISUPPORTS \ + NS_DECL_CYCLE_COLLECTING_ISUPPORTS_META(override) \ + NS_IMETHOD_(void) DeleteCycleCollectable(void); \ + \ + public: + +#define NS_DECL_CYCLE_COLLECTING_ISUPPORTS_FINAL \ + NS_DECL_CYCLE_COLLECTING_ISUPPORTS_META(final) \ + NS_IMETHOD_(void) DeleteCycleCollectable(void); \ + \ + public: + +#define NS_DECL_CYCLE_COLLECTING_ISUPPORTS_META(...) \ + public: \ + NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) __VA_ARGS__; \ + NS_IMETHOD_(MozExternalRefCountType) AddRef(void) __VA_ARGS__; \ + NS_IMETHOD_(MozExternalRefCountType) Release(void) __VA_ARGS__; \ + using HasThreadSafeRefCnt = std::false_type; \ + \ + protected: \ + nsCycleCollectingAutoRefCnt mRefCnt; \ + NS_DECL_OWNINGTHREAD \ + public: + +/////////////////////////////////////////////////////////////////////////////// + +/* + * Implementation of AddRef and Release for non-nsISupports (ie "native") + * cycle-collected classes that use the purple buffer to avoid leaks. + */ + +#define NS_IMPL_CC_NATIVE_ADDREF_BODY(_class) \ + MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(_class) \ + MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); \ + NS_ASSERT_OWNINGTHREAD(_class); \ + nsrefcnt count = \ + mRefCnt.incr(static_cast<void*>(this), \ + _class::NS_CYCLE_COLLECTION_INNERCLASS::GetParticipant()); \ + NS_LOG_ADDREF(this, count, #_class, sizeof(*this)); \ + return count; + +#define NS_IMPL_CC_NATIVE_RELEASE_BODY(_class) \ + MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); \ + NS_ASSERT_OWNINGTHREAD(_class); \ + nsrefcnt count = \ + mRefCnt.decr(static_cast<void*>(this), \ + _class::NS_CYCLE_COLLECTION_INNERCLASS::GetParticipant()); \ + NS_LOG_RELEASE(this, count, #_class); \ + return count; + +#define NS_IMPL_CYCLE_COLLECTING_NATIVE_ADDREF(_class) \ + NS_METHOD_(MozExternalRefCountType) _class::AddRef(void) { \ + NS_IMPL_CC_NATIVE_ADDREF_BODY(_class) \ + } + +#define NS_IMPL_CYCLE_COLLECTING_NATIVE_RELEASE_WITH_LAST_RELEASE(_class, \ + _last) \ + NS_METHOD_(MozExternalRefCountType) _class::Release(void) { \ + MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); \ + NS_ASSERT_OWNINGTHREAD(_class); \ + bool shouldDelete = false; \ + nsrefcnt count = \ + mRefCnt.decr(static_cast<void*>(this), \ + _class::NS_CYCLE_COLLECTION_INNERCLASS::GetParticipant(), \ + &shouldDelete); \ + NS_LOG_RELEASE(this, count, #_class); \ + if (count == 0) { \ + mRefCnt.incr(static_cast<void*>(this), \ + _class::NS_CYCLE_COLLECTION_INNERCLASS::GetParticipant()); \ + _last; \ + mRefCnt.decr(static_cast<void*>(this), \ + _class::NS_CYCLE_COLLECTION_INNERCLASS::GetParticipant()); \ + if (shouldDelete) { \ + mRefCnt.stabilizeForDeletion(); \ + DeleteCycleCollectable(); \ + } \ + } \ + return count; \ + } + +#define NS_IMPL_CYCLE_COLLECTING_NATIVE_RELEASE(_class) \ + NS_METHOD_(MozExternalRefCountType) _class::Release(void) { \ + NS_IMPL_CC_NATIVE_RELEASE_BODY(_class) \ + } + +#define NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(_class) \ + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING_META(_class, NS_METHOD_) + +#define NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING_VIRTUAL(_class) \ + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING_META(_class, NS_IMETHOD_) + +#define NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING_INHERITED(_class) \ + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING_META(_class, NS_METHOD_, \ + override) + +#define NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING_META(_class, _decl, \ + ...) \ + public: \ + _decl(MozExternalRefCountType) AddRef(void) __VA_ARGS__{ \ + NS_IMPL_CC_NATIVE_ADDREF_BODY(_class)} _decl(MozExternalRefCountType) \ + Release(void) __VA_ARGS__ { \ + NS_IMPL_CC_NATIVE_RELEASE_BODY(_class) \ + } \ + using HasThreadSafeRefCnt = std::false_type; \ + \ + protected: \ + nsCycleCollectingAutoRefCnt mRefCnt; \ + NS_DECL_OWNINGTHREAD \ + public: + +/////////////////////////////////////////////////////////////////////////////// + +/** + * Use this macro to declare and implement the AddRef & Release methods for a + * given non-XPCOM <i>_class</i>. + * + * @param _class The name of the class implementing the method + * @param _destroy A statement that is executed when the object's + * refcount drops to zero. + * @param _decl Name of the macro to be used for the return type of the + * AddRef & Release methods (typically NS_IMETHOD_ or NS_METHOD_). + * @param optional override Mark the AddRef & Release methods as overrides. + */ +#define NS_INLINE_DECL_REFCOUNTING_META(_class, _decl, _destroy, _owning, ...) \ + public: \ + _decl(MozExternalRefCountType) AddRef(void) __VA_ARGS__ { \ + MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(_class) \ + MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); \ + NS_ASSERT_OWNINGTHREAD(_class); \ + ++mRefCnt; \ + NS_LOG_ADDREF(this, mRefCnt, #_class, sizeof(*this)); \ + return mRefCnt; \ + } \ + _decl(MozExternalRefCountType) Release(void) __VA_ARGS__ { \ + MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); \ + NS_ASSERT_OWNINGTHREAD(_class); \ + --mRefCnt; \ + NS_LOG_RELEASE(this, mRefCnt, #_class); \ + if (mRefCnt == 0) { \ + mRefCnt = 1; /* stabilize */ \ + _destroy; \ + return 0; \ + } \ + return mRefCnt; \ + } \ + using HasThreadSafeRefCnt = std::false_type; \ + \ + protected: \ + nsAutoRefCnt mRefCnt; \ + _owning public: + +/** + * Use this macro to declare and implement the AddRef & Release methods for a + * given non-XPCOM <i>_class</i>. + * + * @param _class The name of the class implementing the method + * @param _destroy A statement that is executed when the object's + * refcount drops to zero. + * @param optional override Mark the AddRef & Release methods as overrides. + */ +#define NS_INLINE_DECL_REFCOUNTING_WITH_DESTROY(_class, _destroy, ...) \ + NS_INLINE_DECL_REFCOUNTING_META(_class, NS_METHOD_, _destroy, \ + NS_DECL_OWNINGTHREAD, __VA_ARGS__) + +/** + * Like NS_INLINE_DECL_REFCOUNTING_WITH_DESTROY with AddRef & Release declared + * virtual. + */ +#define NS_INLINE_DECL_VIRTUAL_REFCOUNTING_WITH_DESTROY(_class, _destroy, ...) \ + NS_INLINE_DECL_REFCOUNTING_META(_class, NS_IMETHOD_, _destroy, \ + NS_DECL_OWNINGTHREAD, __VA_ARGS__) + +/** + * Use this macro to declare and implement the AddRef & Release methods for a + * given non-XPCOM <i>_class</i>. + * + * @param _class The name of the class implementing the method + * @param optional override Mark the AddRef & Release methods as overrides. + */ +#define NS_INLINE_DECL_REFCOUNTING(_class, ...) \ + NS_INLINE_DECL_REFCOUNTING_WITH_DESTROY(_class, delete (this), __VA_ARGS__) + +/** + * Like NS_INLINE_DECL_REFCOUNTING, however the thread safety check will work + * with any nsISerialEventTarget. This is a workaround until bug 1648031 is + * properly resolved. Once this is done, it will be possible to use + * NS_INLINE_DECL_REFCOUNTING under all circumstances. + */ +#define NS_INLINE_DECL_REFCOUNTING_ONEVENTTARGET(_class, ...) \ + NS_INLINE_DECL_REFCOUNTING_META(_class, NS_METHOD_, delete (this), \ + NS_DECL_OWNINGEVENTTARGET, __VA_ARGS__) + +#define NS_INLINE_DECL_THREADSAFE_REFCOUNTING_META(_class, _decl, _destroy, \ + ...) \ + public: \ + _decl(MozExternalRefCountType) AddRef(void) __VA_ARGS__ { \ + MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(_class) \ + MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); \ + nsrefcnt count = ++mRefCnt; \ + NS_LOG_ADDREF(this, count, #_class, sizeof(*this)); \ + return (nsrefcnt)count; \ + } \ + _decl(MozExternalRefCountType) Release(void) __VA_ARGS__ { \ + MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); \ + nsrefcnt count = --mRefCnt; \ + NS_LOG_RELEASE(this, count, #_class); \ + if (count == 0) { \ + _destroy; \ + return 0; \ + } \ + return count; \ + } \ + using HasThreadSafeRefCnt = std::true_type; \ + \ + protected: \ + ::mozilla::ThreadSafeAutoRefCnt mRefCnt; \ + \ + public: + +/** + * Use this macro to declare and implement the AddRef & Release methods for a + * given non-XPCOM <i>_class</i> in a threadsafe manner. + * + * DOES NOT DO REFCOUNT STABILIZATION! + * + * @param _class The name of the class implementing the method + * @param _destroy A statement that is executed when the object's + * refcount drops to zero. + * @param optional override Mark the AddRef & Release methods as overrides. + */ +#define NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DESTROY(_class, _destroy, \ + ...) \ + NS_INLINE_DECL_THREADSAFE_REFCOUNTING_META(_class, NS_METHOD_, _destroy, \ + __VA_ARGS__) + +/** + * Like NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DESTROY with AddRef & Release + * declared virtual. + */ +#define NS_INLINE_DECL_THREADSAFE_VIRTUAL_REFCOUNTING_WITH_DESTROY( \ + _class, _destroy, ...) \ + NS_INLINE_DECL_THREADSAFE_REFCOUNTING_META(_class, NS_IMETHOD_, _destroy, \ + __VA_ARGS__) + +/** + * Use this macro to declare and implement the AddRef & Release methods for a + * given non-XPCOM <i>_class</i> in a threadsafe manner. + * + * DOES NOT DO REFCOUNT STABILIZATION! + * + * @param _class The name of the class implementing the method + * @param optional override Mark the AddRef & Release methods as overrides. + */ +#define NS_INLINE_DECL_THREADSAFE_REFCOUNTING(_class, ...) \ + NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DESTROY(_class, delete (this), \ + __VA_ARGS__) + +/** + * Like NS_INLINE_DECL_THREADSAFE_REFCOUNTING with AddRef & Release declared + * virtual. + */ +#define NS_INLINE_DECL_THREADSAFE_VIRTUAL_REFCOUNTING(_class, ...) \ + NS_INLINE_DECL_THREADSAFE_VIRTUAL_REFCOUNTING_WITH_DESTROY( \ + _class, delete (this), __VA_ARGS__) + +#if !defined(XPCOM_GLUE_AVOID_NSPR) +class nsISerialEventTarget; +namespace mozilla { +// Forward-declare `GetMainThreadSerialEventTarget`, as `nsISupportsImpl.h` +// cannot include `nsThreadUtils.h`. +nsISerialEventTarget* GetMainThreadSerialEventTarget(); + +namespace detail { +using DeleteVoidFunction = void(void*); +void ProxyDeleteVoid(const char* aRunnableName, + nsISerialEventTarget* aEventTarget, void* aSelf, + DeleteVoidFunction* aDeleteFunc); +} // namespace detail +} // namespace mozilla + +/** + * Helper for _WITH_DELETE_ON_EVENT_TARGET threadsafe refcounting macros which + * provides an implementation of `_destroy` + */ +# define NS_PROXY_DELETE_TO_EVENT_TARGET(_class, _target) \ + ::mozilla::detail::ProxyDeleteVoid( \ + "ProxyDelete " #_class, _target, this, \ + [](void* self) { delete static_cast<_class*>(self); }) + +/** + * Use this macro to declare and implement the AddRef & Release methods for a + * given non-XPCOM <i>_class</i> in a threadsafe manner, ensuring the + * destructor runs on a specific nsISerialEventTarget. + * + * DOES NOT DO REFCOUNT STABILIZATION! + * + * @param _class The name of the class implementing the method + * @param _target nsISerialEventTarget to run the class's destructor on + * @param optional override Mark the AddRef & Release methods as overrides + */ +# define NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DELETE_ON_EVENT_TARGET( \ + _class, _target, ...) \ + NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DESTROY( \ + _class, NS_PROXY_DELETE_TO_EVENT_TARGET(_class, _target), __VA_ARGS__) + +/** + * Use this macro to declare and implement the AddRef & Release methods for a + * given non-XPCOM <i>_class</i> in a threadsafe manner, ensuring the + * destructor runs on the main thread. + * + * DOES NOT DO REFCOUNT STABILIZATION! + * + * @param _class The name of the class implementing the method + * @param optional override Mark the AddRef & Release methods as overrides + */ +# define NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DELETE_ON_MAIN_THREAD( \ + _class, ...) \ + NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DELETE_ON_EVENT_TARGET( \ + _class, ::mozilla::GetMainThreadSerialEventTarget(), __VA_ARGS__) +#endif + +/** + * Use this macro in interface classes that you want to be able to reference + * using RefPtr, but don't want to provide a refcounting implemenation. The + * refcounting implementation can be provided by concrete subclasses that + * implement the interface. + */ +#define NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING \ + public: \ + NS_IMETHOD_(MozExternalRefCountType) AddRef(void) = 0; \ + NS_IMETHOD_(MozExternalRefCountType) Release(void) = 0; \ + \ + public: + +/** + * Use this macro to implement the AddRef method for a given <i>_class</i> + * @param _class The name of the class implementing the method + * @param _name The class name to be passed to XPCOM leak checking + */ +#define NS_IMPL_NAMED_ADDREF(_class, _name) \ + NS_IMETHODIMP_(MozExternalRefCountType) _class::AddRef(void) { \ + MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(_class) \ + MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); \ + MOZ_ASSERT(_name != nullptr, "Must specify a name"); \ + if (!mRefCnt.isThreadSafe) NS_ASSERT_OWNINGTHREAD(_class); \ + nsrefcnt count = ++mRefCnt; \ + NS_LOG_ADDREF(this, count, _name, sizeof(*this)); \ + return count; \ + } + +/** + * Use this macro to implement the AddRef method for a given <i>_class</i> + * @param _class The name of the class implementing the method + */ +#define NS_IMPL_ADDREF(_class) NS_IMPL_NAMED_ADDREF(_class, #_class) + +/** + * Use this macro to implement the AddRef method for a given <i>_class</i> + * implemented as a wholly owned aggregated object intended to implement + * interface(s) for its owner + * @param _class The name of the class implementing the method + * @param _aggregator the owning/containing object + */ +#define NS_IMPL_ADDREF_USING_AGGREGATOR(_class, _aggregator) \ + NS_IMETHODIMP_(MozExternalRefCountType) _class::AddRef(void) { \ + MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(_class) \ + MOZ_ASSERT(_aggregator, "null aggregator"); \ + return (_aggregator)->AddRef(); \ + } + +// We decrement the refcnt before logging the actual release, but when logging +// named things, accessing the name may not be valid after the refcnt +// decrement, because the object may have been destroyed on a different thread. +// Use this macro to ensure that we have a local copy of the name prior to +// the refcnt decrement. (We use a macro to make absolutely sure the name +// isn't loaded in builds where it wouldn't be used.) +#ifdef NS_BUILD_REFCNT_LOGGING +# define NS_LOAD_NAME_BEFORE_RELEASE(localname, _name) \ + const char* const localname = _name +#else +# define NS_LOAD_NAME_BEFORE_RELEASE(localname, _name) +#endif + +/** + * Use this macro to implement the Release method for a given + * <i>_class</i>. + * @param _class The name of the class implementing the method + * @param _name The class name to be passed to XPCOM leak checking + * @param _destroy A statement that is executed when the object's + * refcount drops to zero. + * + * For example, + * + * NS_IMPL_RELEASE_WITH_DESTROY(Foo, "Foo", Destroy(this)) + * + * will cause + * + * Destroy(this); + * + * to be invoked when the object's refcount drops to zero. This + * allows for arbitrary teardown activity to occur (e.g., deallocation + * of object allocated with placement new). + */ +#define NS_IMPL_NAMED_RELEASE_WITH_DESTROY(_class, _name, _destroy) \ + NS_IMETHODIMP_(MozExternalRefCountType) _class::Release(void) { \ + MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); \ + MOZ_ASSERT(_name != nullptr, "Must specify a name"); \ + if (!mRefCnt.isThreadSafe) NS_ASSERT_OWNINGTHREAD(_class); \ + NS_LOAD_NAME_BEFORE_RELEASE(nametmp, _name); \ + nsrefcnt count = --mRefCnt; \ + NS_LOG_RELEASE(this, count, nametmp); \ + if (count == 0) { \ + mRefCnt = 1; /* stabilize */ \ + _destroy; \ + return 0; \ + } \ + return count; \ + } + +#define NS_IMPL_RELEASE_WITH_DESTROY(_class, _destroy) \ + NS_IMPL_NAMED_RELEASE_WITH_DESTROY(_class, #_class, _destroy) + +/** + * Use this macro to implement the Release method for a given <i>_class</i> + * @param _class The name of the class implementing the method + * + * A note on the 'stabilization' of the refcnt to one. At that point, + * the object's refcount will have gone to zero. The object's + * destructor may trigger code that attempts to QueryInterface() and + * Release() 'this' again. Doing so will temporarily increment and + * decrement the refcount. (Only a logic error would make one try to + * keep a permanent hold on 'this'.) To prevent re-entering the + * destructor, we make sure that no balanced refcounting can return + * the refcount to |0|. + */ +#define NS_IMPL_RELEASE(_class) \ + NS_IMPL_RELEASE_WITH_DESTROY(_class, delete (this)) + +#define NS_IMPL_NAMED_RELEASE(_class, _name) \ + NS_IMPL_NAMED_RELEASE_WITH_DESTROY(_class, _name, delete (this)) + +/** + * Use this macro to implement the Release method for a given <i>_class</i> + * implemented as a wholly owned aggregated object intended to implement + * interface(s) for its owner + * @param _class The name of the class implementing the method + * @param _aggregator the owning/containing object + */ +#define NS_IMPL_RELEASE_USING_AGGREGATOR(_class, _aggregator) \ + NS_IMETHODIMP_(MozExternalRefCountType) _class::Release(void) { \ + MOZ_ASSERT(_aggregator, "null aggregator"); \ + return (_aggregator)->Release(); \ + } + +#define NS_IMPL_CYCLE_COLLECTING_ADDREF(_class) \ + NS_IMETHODIMP_(MozExternalRefCountType) _class::AddRef(void) { \ + MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(_class) \ + MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); \ + NS_ASSERT_OWNINGTHREAD(_class); \ + nsISupports* base = NS_CYCLE_COLLECTION_CLASSNAME(_class)::Upcast(this); \ + nsrefcnt count = mRefCnt.incr(base); \ + NS_LOG_ADDREF(this, count, #_class, sizeof(*this)); \ + return count; \ + } + +#define NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_DESTROY(_class, _destroy) \ + NS_IMETHODIMP_(MozExternalRefCountType) _class::Release(void) { \ + MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); \ + NS_ASSERT_OWNINGTHREAD(_class); \ + nsISupports* base = NS_CYCLE_COLLECTION_CLASSNAME(_class)::Upcast(this); \ + nsrefcnt count = mRefCnt.decr(base); \ + NS_LOG_RELEASE(this, count, #_class); \ + return count; \ + } \ + NS_IMETHODIMP_(void) _class::DeleteCycleCollectable(void) { _destroy; } + +#define NS_IMPL_CYCLE_COLLECTING_RELEASE(_class) \ + NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_DESTROY(_class, delete (this)) + +// _LAST_RELEASE can be useful when certain resources should be released +// as soon as we know the object will be deleted. +#define NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(_class, _last) \ + NS_IMETHODIMP_(MozExternalRefCountType) _class::Release(void) { \ + MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); \ + NS_ASSERT_OWNINGTHREAD(_class); \ + bool shouldDelete = false; \ + nsISupports* base = NS_CYCLE_COLLECTION_CLASSNAME(_class)::Upcast(this); \ + nsrefcnt count = mRefCnt.decr(base, &shouldDelete); \ + NS_LOG_RELEASE(this, count, #_class); \ + if (count == 0) { \ + mRefCnt.incr(base); \ + _last; \ + mRefCnt.decr(base); \ + if (shouldDelete) { \ + mRefCnt.stabilizeForDeletion(); \ + DeleteCycleCollectable(); \ + } \ + } \ + return count; \ + } \ + NS_IMETHODIMP_(void) _class::DeleteCycleCollectable(void) { delete this; } + +// This macro is same as NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE +// except it doesn't have DeleteCycleCollectable. +#define NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE_AND_DESTROY( \ + _class, _last, _destroy) \ + NS_IMETHODIMP_(MozExternalRefCountType) _class::Release(void) { \ + MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); \ + NS_ASSERT_OWNINGTHREAD(_class); \ + bool shouldDelete = false; \ + nsISupports* base = NS_CYCLE_COLLECTION_CLASSNAME(_class)::Upcast(this); \ + nsrefcnt count = mRefCnt.decr(base, &shouldDelete); \ + NS_LOG_RELEASE(this, count, #_class); \ + if (count == 0) { \ + mRefCnt.incr(base); \ + _last; \ + mRefCnt.decr(base); \ + if (shouldDelete) { \ + mRefCnt.stabilizeForDeletion(); \ + DeleteCycleCollectable(); \ + } \ + } \ + return count; \ + } \ + NS_IMETHODIMP_(void) _class::DeleteCycleCollectable(void) { _destroy; } + +// _WITH_INTERRUPTABLE_LAST_RELEASE can be useful when certain resources +// should be released as soon as we know the object will be deleted and the +// instance may be cached for reuse. +// _last is performed for cleaning up its resources. Then, _maybeInterrupt is +// tested and when it returns true, this stops deleting the instance. +// (Note that it's not allowed to grab the instance with nsCOMPtr or RefPtr +// during _last is performed.) +// Therefore, when _maybeInterrupt returns true, the instance has to be grabbed +// by nsCOMPtr or RefPtr. +#define NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_INTERRUPTABLE_LAST_RELEASE( \ + _class, _last, _maybeInterrupt) \ + NS_IMETHODIMP_(MozExternalRefCountType) _class::Release(void) { \ + MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); \ + NS_ASSERT_OWNINGTHREAD(_class); \ + bool shouldDelete = false; \ + nsISupports* base = NS_CYCLE_COLLECTION_CLASSNAME(_class)::Upcast(this); \ + nsrefcnt count = mRefCnt.decr(base, &shouldDelete); \ + NS_LOG_RELEASE(this, count, #_class); \ + if (count == 0) { \ + mRefCnt.incr(base); \ + _last; \ + mRefCnt.decr(base); \ + if (_maybeInterrupt) { \ + MOZ_ASSERT(mRefCnt.get() > 0); \ + return mRefCnt.get(); \ + } \ + if (shouldDelete) { \ + mRefCnt.stabilizeForDeletion(); \ + DeleteCycleCollectable(); \ + } \ + } \ + return count; \ + } \ + NS_IMETHODIMP_(void) _class::DeleteCycleCollectable(void) { delete this; } + +/////////////////////////////////////////////////////////////////////////////// + +namespace mozilla::detail { + +// Helper which is roughly equivalent to NS_GET_IID, which also performs static +// assertions that `Class` is allowed to implement the given XPCOM interface. +// +// These assertions are done like this to allow them to be used within the +// `NS_INTERFACE_TABLE_ENTRY` macro, though they are also used in +// `NS_IMPL_QUERY_BODY`. +template <typename Class, typename Interface> +constexpr const nsIID& GetImplementedIID() { + if constexpr (mozilla::detail::InterfaceNeedsThreadSafeRefCnt< + Interface>::value) { + static_assert(Class::HasThreadSafeRefCnt::value, + "Cannot implement a threadsafe interface with " + "non-threadsafe refcounting!"); + } + return NS_GET_TEMPLATE_IID(Interface); +} + +template <typename Class, typename Interface> +constexpr const nsIID& kImplementedIID = GetImplementedIID<Class, Interface>(); + +} + +/** + * There are two ways of implementing QueryInterface, and we use both: + * + * Table-driven QueryInterface uses a static table of IID->offset mappings + * and a shared helper function. Using it tends to reduce codesize and improve + * runtime performance (due to processor cache hits). + * + * Macro-driven QueryInterface generates a QueryInterface function directly + * using common macros. This is necessary if special QueryInterface features + * are being used (such as tearoffs and conditional interfaces). + * + * These methods can be combined into a table-driven function call followed + * by custom code for tearoffs and conditionals. + */ + +struct QITableEntry { + const nsIID* iid; // null indicates end of the QITableEntry array + int32_t offset; +}; + +nsresult NS_FASTCALL NS_TableDrivenQI(void* aThis, REFNSIID aIID, + void** aInstancePtr, + const QITableEntry* aEntries); + +/** + * Implement table-driven queryinterface + */ + +#define NS_INTERFACE_TABLE_HEAD(_class) \ + NS_IMETHODIMP _class::QueryInterface(REFNSIID aIID, void** aInstancePtr) { \ + NS_ASSERTION(aInstancePtr, \ + "QueryInterface requires a non-NULL destination!"); \ + nsresult rv = NS_ERROR_FAILURE; + +#define NS_INTERFACE_TABLE_BEGIN static const QITableEntry table[] = { +#define NS_INTERFACE_TABLE_ENTRY(_class, _interface) \ + {&mozilla::detail::kImplementedIID<_class, _interface>, \ + int32_t( \ + reinterpret_cast<char*>(static_cast<_interface*>((_class*)0x1000)) - \ + reinterpret_cast<char*>((_class*)0x1000))}, + +#define NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(_class, _interface, _implClass) \ + {&mozilla::detail::kImplementedIID<_class, _interface>, \ + int32_t(reinterpret_cast<char*>(static_cast<_interface*>( \ + static_cast<_implClass*>((_class*)0x1000))) - \ + reinterpret_cast<char*>((_class*)0x1000))}, + +/* + * XXX: we want to use mozilla::ArrayLength (or equivalent, + * MOZ_ARRAY_LENGTH) in this condition, but some versions of GCC don't + * see that the static_assert condition is actually constant in those + * cases, even with constexpr support (?). + */ +#define NS_INTERFACE_TABLE_END_WITH_PTR(_ptr) \ + { nullptr, 0 } \ + } \ + ; \ + static_assert((sizeof(table) / sizeof(table[0])) > 1, \ + "need at least 1 interface"); \ + rv = NS_TableDrivenQI(static_cast<void*>(_ptr), aIID, aInstancePtr, table); + +#define NS_INTERFACE_TABLE_END \ + NS_INTERFACE_TABLE_END_WITH_PTR \ + (this) + +#define NS_INTERFACE_TABLE_TAIL \ + return rv; \ + } + +#define NS_INTERFACE_TABLE_TAIL_INHERITING(_baseclass) \ + if (NS_SUCCEEDED(rv)) return rv; \ + return _baseclass::QueryInterface(aIID, aInstancePtr); \ + } + +#define NS_INTERFACE_TABLE_TAIL_USING_AGGREGATOR(_aggregator) \ + if (NS_SUCCEEDED(rv)) return rv; \ + NS_ASSERTION(_aggregator, "null aggregator"); \ + return _aggregator->QueryInterface(aIID, aInstancePtr) \ + } + +/** + * This implements query interface with two assumptions: First, the + * class in question implements nsISupports and its own interface and + * nothing else. Second, the implementation of the class's primary + * inheritance chain leads to its own interface. + * + * @param _class The name of the class implementing the method + * @param _classiiddef The name of the #define symbol that defines the IID + * for the class (e.g. NS_ISUPPORTS_IID) + */ + +#define NS_IMPL_QUERY_HEAD(_class) \ + NS_IMETHODIMP _class::QueryInterface(REFNSIID aIID, void** aInstancePtr) { \ + NS_ASSERTION(aInstancePtr, \ + "QueryInterface requires a non-NULL destination!"); \ + nsISupports* foundInterface; + +#define NS_IMPL_QUERY_BODY_IID(_interface) \ + mozilla::detail::kImplementedIID<std::remove_reference_t<decltype(*this)>, \ + _interface> + +#define NS_IMPL_QUERY_BODY(_interface) \ + if (aIID.Equals(NS_IMPL_QUERY_BODY_IID(_interface))) \ + foundInterface = static_cast<_interface*>(this); \ + else + +#define NS_IMPL_QUERY_BODY_CONDITIONAL(_interface, condition) \ + if ((condition) && aIID.Equals(NS_IMPL_QUERY_BODY_IID(_interface))) \ + foundInterface = static_cast<_interface*>(this); \ + else + +#define NS_IMPL_QUERY_BODY_AMBIGUOUS(_interface, _implClass) \ + if (aIID.Equals(NS_IMPL_QUERY_BODY_IID(_interface))) \ + foundInterface = static_cast<_interface*>(static_cast<_implClass*>(this)); \ + else + +// Use this for querying to concrete class types which cannot be unambiguously +// cast to nsISupports. See also nsQueryObject.h. +#define NS_IMPL_QUERY_BODY_CONCRETE(_class) \ + if (aIID.Equals(NS_IMPL_QUERY_BODY_IID(_class))) { \ + *aInstancePtr = do_AddRef(static_cast<_class*>(this)).take(); \ + return NS_OK; \ + } else + +#define NS_IMPL_QUERY_BODY_AGGREGATED(_interface, _aggregate) \ + if (aIID.Equals(NS_IMPL_QUERY_BODY_IID(_interface))) \ + foundInterface = static_cast<_interface*>(_aggregate); \ + else + +#define NS_IMPL_QUERY_TAIL_GUTS \ + foundInterface = 0; \ + nsresult status; \ + if (!foundInterface) { \ + /* nsISupports should be handled by this point. If not, fail. */ \ + MOZ_ASSERT(!aIID.Equals(NS_GET_IID(nsISupports))); \ + status = NS_NOINTERFACE; \ + } else { \ + NS_ADDREF(foundInterface); \ + status = NS_OK; \ + } \ + *aInstancePtr = foundInterface; \ + return status; \ + } + +#define NS_IMPL_QUERY_TAIL_INHERITING(_baseclass) \ + foundInterface = 0; \ + nsresult status; \ + if (!foundInterface) \ + status = _baseclass::QueryInterface(aIID, (void**)&foundInterface); \ + else { \ + NS_ADDREF(foundInterface); \ + status = NS_OK; \ + } \ + *aInstancePtr = foundInterface; \ + return status; \ + } + +#define NS_IMPL_QUERY_TAIL_USING_AGGREGATOR(_aggregator) \ + foundInterface = 0; \ + nsresult status; \ + if (!foundInterface) { \ + NS_ASSERTION(_aggregator, "null aggregator"); \ + status = _aggregator->QueryInterface(aIID, (void**)&foundInterface); \ + } else { \ + NS_ADDREF(foundInterface); \ + status = NS_OK; \ + } \ + *aInstancePtr = foundInterface; \ + return status; \ + } + +#define NS_IMPL_QUERY_TAIL(_supports_interface) \ + NS_IMPL_QUERY_BODY_AMBIGUOUS(nsISupports, _supports_interface) \ + NS_IMPL_QUERY_TAIL_GUTS + +/* + This is the new scheme. Using this notation now will allow us to switch to + a table driven mechanism when it's ready. Note the difference between this + and the (currently) underlying NS_IMPL_QUERY_INTERFACE mechanism. You must + explicitly mention |nsISupports| when using the interface maps. +*/ +#define NS_INTERFACE_MAP_BEGIN(_implClass) NS_IMPL_QUERY_HEAD(_implClass) +#define NS_INTERFACE_MAP_ENTRY(_interface) NS_IMPL_QUERY_BODY(_interface) +#define NS_INTERFACE_MAP_ENTRY_CONDITIONAL(_interface, condition) \ + NS_IMPL_QUERY_BODY_CONDITIONAL(_interface, condition) +#define NS_INTERFACE_MAP_ENTRY_AGGREGATED(_interface, _aggregate) \ + NS_IMPL_QUERY_BODY_AGGREGATED(_interface, _aggregate) + +#define NS_INTERFACE_MAP_END NS_IMPL_QUERY_TAIL_GUTS +#define NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(_interface, _implClass) \ + NS_IMPL_QUERY_BODY_AMBIGUOUS(_interface, _implClass) +#define NS_INTERFACE_MAP_ENTRY_CONCRETE(_class) \ + NS_IMPL_QUERY_BODY_CONCRETE(_class) +#define NS_INTERFACE_MAP_END_INHERITING(_baseClass) \ + NS_IMPL_QUERY_TAIL_INHERITING(_baseClass) +#define NS_INTERFACE_MAP_END_AGGREGATED(_aggregator) \ + NS_IMPL_QUERY_TAIL_USING_AGGREGATOR(_aggregator) + +#define NS_INTERFACE_TABLE0(_class) \ + NS_INTERFACE_TABLE_BEGIN \ + NS_INTERFACE_TABLE_ENTRY(_class, nsISupports) \ + NS_INTERFACE_TABLE_END + +#define NS_INTERFACE_TABLE(aClass, ...) \ + static_assert(MOZ_ARG_COUNT(__VA_ARGS__) > 0, \ + "Need more arguments to NS_INTERFACE_TABLE"); \ + NS_INTERFACE_TABLE_BEGIN \ + MOZ_FOR_EACH(NS_INTERFACE_TABLE_ENTRY, (aClass, ), (__VA_ARGS__)) \ + NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(aClass, nsISupports, \ + MOZ_ARG_1(__VA_ARGS__)) \ + NS_INTERFACE_TABLE_END + +#define NS_IMPL_QUERY_INTERFACE0(_class) \ + NS_INTERFACE_TABLE_HEAD(_class) \ + NS_INTERFACE_TABLE0(_class) \ + NS_INTERFACE_TABLE_TAIL + +#define NS_IMPL_QUERY_INTERFACE(aClass, ...) \ + NS_INTERFACE_TABLE_HEAD(aClass) \ + NS_INTERFACE_TABLE(aClass, __VA_ARGS__) \ + NS_INTERFACE_TABLE_TAIL + +/** + * Declare that you're going to inherit from something that already + * implements nsISupports, but also implements an additional interface, thus + * causing an ambiguity. In this case you don't need another mRefCnt, you + * just need to forward the definitions to the appropriate superclass. E.g. + * + * class Bar : public Foo, public nsIBar { // both provide nsISupports + * public: + * NS_DECL_ISUPPORTS_INHERITED + * ...other nsIBar and Bar methods... + * }; + */ +#define NS_DECL_ISUPPORTS_INHERITED \ + public: \ + NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override; \ + NS_IMETHOD_(MozExternalRefCountType) AddRef(void) override; \ + NS_IMETHOD_(MozExternalRefCountType) Release(void) override; + +/** + * These macros can be used in conjunction with NS_DECL_ISUPPORTS_INHERITED + * to implement the nsISupports methods, forwarding the invocations to a + * superclass that already implements nsISupports. Don't do anything for + * subclasses of Runnable because it deals with subclass logging in its own + * way, using the mName field. + * + * Note that I didn't make these inlined because they're virtual methods. + */ + +namespace mozilla { +class Runnable; +namespace detail { +class SupportsThreadSafeWeakPtrBase; + +// Don't NS_LOG_{ADDREF,RELEASE} when inheriting from `Runnable*` or types with +// thread safe weak references, as it will generate incorrect refcnt logs due to +// the thread-safe `Upgrade()` call's refcount modifications not calling through +// the derived class' `AddRef()` and `Release()` methods. +template <typename T> +constexpr bool ShouldLogInheritedRefcnt = + !std::is_convertible_v<T*, Runnable*> && + !std::is_base_of_v<SupportsThreadSafeWeakPtrBase, T>; +} +} // namespace mozilla + +#define NS_IMPL_ADDREF_INHERITED_GUTS(Class, Super) \ + MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(Class) \ + nsrefcnt r = Super::AddRef(); \ + if constexpr (::mozilla::detail::ShouldLogInheritedRefcnt<Class>) { \ + NS_LOG_ADDREF(this, r, #Class, sizeof(*this)); \ + } \ + return r /* Purposefully no trailing semicolon */ + +#define NS_IMPL_ADDREF_INHERITED(Class, Super) \ + NS_IMETHODIMP_(MozExternalRefCountType) Class::AddRef(void) { \ + NS_IMPL_ADDREF_INHERITED_GUTS(Class, Super); \ + } + +#define NS_IMPL_RELEASE_INHERITED_GUTS(Class, Super) \ + nsrefcnt r = Super::Release(); \ + if constexpr (::mozilla::detail::ShouldLogInheritedRefcnt<Class>) { \ + NS_LOG_RELEASE(this, r, #Class); \ + } \ + return r /* Purposefully no trailing semicolon */ + +#define NS_IMPL_RELEASE_INHERITED(Class, Super) \ + NS_IMETHODIMP_(MozExternalRefCountType) Class::Release(void) { \ + NS_IMPL_RELEASE_INHERITED_GUTS(Class, Super); \ + } + +/** + * As above but not logging the addref/release; needed if the base + * class might be aggregated. + */ +#define NS_IMPL_NONLOGGING_ADDREF_INHERITED(Class, Super) \ + NS_IMETHODIMP_(MozExternalRefCountType) Class::AddRef(void) { \ + MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(Class) \ + return Super::AddRef(); \ + } + +#define NS_IMPL_NONLOGGING_RELEASE_INHERITED(Class, Super) \ + NS_IMETHODIMP_(MozExternalRefCountType) Class::Release(void) { \ + return Super::Release(); \ + } + +#define NS_INTERFACE_TABLE_INHERITED0(Class) /* Nothing to do here */ + +#define NS_INTERFACE_TABLE_INHERITED(aClass, ...) \ + static_assert(MOZ_ARG_COUNT(__VA_ARGS__) > 0, \ + "Need more arguments to NS_INTERFACE_TABLE_INHERITED"); \ + NS_INTERFACE_TABLE_BEGIN \ + MOZ_FOR_EACH(NS_INTERFACE_TABLE_ENTRY, (aClass, ), (__VA_ARGS__)) \ + NS_INTERFACE_TABLE_END + +#define NS_IMPL_QUERY_INTERFACE_INHERITED(aClass, aSuper, ...) \ + NS_INTERFACE_TABLE_HEAD(aClass) \ + NS_INTERFACE_TABLE_INHERITED(aClass, __VA_ARGS__) \ + NS_INTERFACE_TABLE_TAIL_INHERITING(aSuper) + +/** + * Convenience macros for implementing all nsISupports methods for + * a simple class. + * @param _class The name of the class implementing the method + * @param _classiiddef The name of the #define symbol that defines the IID + * for the class (e.g. NS_ISUPPORTS_IID) + */ + +#define NS_IMPL_ISUPPORTS0(_class) \ + NS_IMPL_ADDREF(_class) \ + NS_IMPL_RELEASE(_class) \ + NS_IMPL_QUERY_INTERFACE0(_class) + +#define NS_IMPL_ISUPPORTS(aClass, ...) \ + NS_IMPL_ADDREF(aClass) \ + NS_IMPL_RELEASE(aClass) \ + NS_IMPL_QUERY_INTERFACE(aClass, __VA_ARGS__) + +// When possible, prefer NS_INLINE_DECL_REFCOUNTING_INHERITED to +// NS_IMPL_ISUPPORTS_INHERITED0. +#define NS_IMPL_ISUPPORTS_INHERITED0(aClass, aSuper) \ + NS_INTERFACE_TABLE_HEAD(aClass) \ + NS_INTERFACE_TABLE_TAIL_INHERITING(aSuper) \ + NS_IMPL_ADDREF_INHERITED(aClass, aSuper) \ + NS_IMPL_RELEASE_INHERITED(aClass, aSuper) + +#define NS_IMPL_ISUPPORTS_INHERITED(aClass, aSuper, ...) \ + NS_IMPL_QUERY_INTERFACE_INHERITED(aClass, aSuper, __VA_ARGS__) \ + NS_IMPL_ADDREF_INHERITED(aClass, aSuper) \ + NS_IMPL_RELEASE_INHERITED(aClass, aSuper) + +/** + * A macro to declare and implement inherited addref/release for a class which + * doesn't have or need to override QueryInterface from its base class. + * + * Note: This macro always overrides the `AddRef` and `Release` methods, + * including when refcount logging is disabled, meaning that it will implement + * the `AddRef` or `Release` method from another virtual base class. + */ +#define NS_INLINE_DECL_REFCOUNTING_INHERITED(Class, Super) \ + NS_IMETHOD_(MozExternalRefCountType) AddRef() override { \ + NS_IMPL_ADDREF_INHERITED_GUTS(Class, Super); \ + } \ + NS_IMETHOD_(MozExternalRefCountType) Release() override { \ + NS_IMPL_RELEASE_INHERITED_GUTS(Class, Super); \ + } + +/* + * Macro to glue together a QI that starts with an interface table + * and segues into an interface map (e.g. it uses singleton classinfo + * or tearoffs). + */ +#define NS_INTERFACE_TABLE_TO_MAP_SEGUE \ + if (rv == NS_OK) return rv; \ + nsISupports* foundInterface; + +/////////////////////////////////////////////////////////////////////////////// + +/** + * Macro to generate nsIClassInfo methods for classes which do not have + * corresponding nsIFactory implementations. + */ +#define NS_IMPL_THREADSAFE_CI(_class) \ + NS_IMETHODIMP \ + _class::GetInterfaces(nsTArray<nsIID>& _array) { \ + return NS_CI_INTERFACE_GETTER_NAME(_class)(_array); \ + } \ + \ + NS_IMETHODIMP \ + _class::GetScriptableHelper(nsIXPCScriptable** _retval) { \ + *_retval = nullptr; \ + return NS_OK; \ + } \ + \ + NS_IMETHODIMP \ + _class::GetContractID(nsACString& _contractID) { \ + _contractID.SetIsVoid(true); \ + return NS_OK; \ + } \ + \ + NS_IMETHODIMP \ + _class::GetClassDescription(nsACString& _classDescription) { \ + _classDescription.SetIsVoid(true); \ + return NS_OK; \ + } \ + \ + NS_IMETHODIMP \ + _class::GetClassID(nsCID** _classID) { \ + *_classID = nullptr; \ + return NS_OK; \ + } \ + \ + NS_IMETHODIMP \ + _class::GetFlags(uint32_t* _flags) { \ + *_flags = nsIClassInfo::THREADSAFE; \ + return NS_OK; \ + } \ + \ + NS_IMETHODIMP \ + _class::GetClassIDNoAlloc(nsCID* _classIDNoAlloc) { \ + return NS_ERROR_NOT_AVAILABLE; \ + } + +#endif diff --git a/xpcom/base/nsISupportsUtils.h b/xpcom/base/nsISupportsUtils.h new file mode 100644 index 0000000000..a885c5cb14 --- /dev/null +++ b/xpcom/base/nsISupportsUtils.h @@ -0,0 +1,142 @@ +/* -*- 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 nsISupportsUtils_h__ +#define nsISupportsUtils_h__ + +#include <type_traits> + +#include "nscore.h" +#include "nsIOutputStream.h" +#include "nsISupports.h" +#include "nsError.h" +#include "nsDebug.h" +#include "nsISupportsImpl.h" +#include "mozilla/RefPtr.h" + +/** + * Macro for adding a reference to an interface. + * @param _ptr The interface pointer. + */ +#define NS_ADDREF(_ptr) (_ptr)->AddRef() + +/** + * Macro for adding a reference to this. This macro should be used + * because NS_ADDREF (when tracing) may require an ambiguous cast + * from the pointers primary type to nsISupports. This macro sidesteps + * that entire problem. + */ +#define NS_ADDREF_THIS() AddRef() + +// Making this a |inline| |template| allows |aExpr| to be evaluated only once, +// yet still denies you the ability to |AddRef()| an |nsCOMPtr|. +template <class T> +inline void ns_if_addref(T aExpr) { + if (aExpr) { + aExpr->AddRef(); + } +} + +/** + * Macro for adding a reference to an interface that checks for nullptr. + * @param _expr The interface pointer. + */ +#define NS_IF_ADDREF(_expr) ns_if_addref(_expr) + +/* + * Given these declarations, it explicitly OK and efficient to end a `getter' + * with: + * + * NS_IF_ADDREF(*result = mThing); + * + * even if |mThing| is an |nsCOMPtr|. If |mThing| is an |nsCOMPtr|, however, it + * is still _illegal_ to say |NS_IF_ADDREF(mThing)|. + */ + +/** + * Macro for releasing a reference to an interface. + * @param _ptr The interface pointer. + */ +#define NS_RELEASE(_ptr) \ + do { \ + (_ptr)->Release(); \ + (_ptr) = 0; \ + } while (0) + +/** + * Macro for releasing a reference to this interface. + */ +#define NS_RELEASE_THIS() Release() + +/** + * Macro for releasing a reference to an interface, except that this + * macro preserves the return value from the underlying Release call. + * The interface pointer argument will only be NULLed if the reference count + * goes to zero. + * + * @param _ptr The interface pointer. + * @param _rc The reference count. + */ +#define NS_RELEASE2(_ptr, _rc) \ + do { \ + _rc = (_ptr)->Release(); \ + if (0 == (_rc)) (_ptr) = 0; \ + } while (0) + +/** + * Macro for releasing a reference to an interface that checks for nullptr; + * @param _ptr The interface pointer. + */ +#define NS_IF_RELEASE(_ptr) \ + do { \ + if (_ptr) { \ + (_ptr)->Release(); \ + (_ptr) = 0; \ + } \ + } while (0) + +/* + * Often you have to cast an implementation pointer, e.g., |this|, to an + * |nsISupports*|, but because you have multiple inheritance, a simple cast + * is ambiguous. One could simply say, e.g., (given a base |nsIBase|), + * |static_cast<nsIBase*>(this)|; but that disguises the fact that what + * you are really doing is disambiguating the |nsISupports|. You could make + * that more obvious with a double cast, e.g., |static_cast<nsISupports*> + (* + static_cast<nsIBase*>(this))|, but that is bulky and harder to read... + * + * The following macro is clean, short, and obvious. In the example above, + * you would use it like this: |NS_ISUPPORTS_CAST(nsIBase*, this)|. + */ + +#define NS_ISUPPORTS_CAST(__unambiguousBase, __expr) \ + static_cast<nsISupports*>(static_cast<__unambiguousBase>(__expr)) + +// a type-safe shortcut for calling the |QueryInterface()| member function +template <class T, class DestinationType> +inline nsresult CallQueryInterface(T* aSource, DestinationType** aDestination) { + // We permit nsISupports-to-nsISupports here so that one can still obtain + // the canonical nsISupports pointer with CallQueryInterface. + static_assert( + !(std::is_same_v<DestinationType, T> || + std::is_base_of<DestinationType, T>::value) || + std::is_same_v<DestinationType, nsISupports>, + "don't use CallQueryInterface for compile-time-determinable casts"); + + MOZ_ASSERT(aSource, "null parameter"); + MOZ_ASSERT(aDestination, "null parameter"); + + return aSource->QueryInterface(NS_GET_TEMPLATE_IID(DestinationType), + reinterpret_cast<void**>(aDestination)); +} + +template <class SourceType, class DestinationType> +inline nsresult CallQueryInterface(RefPtr<SourceType>& aSourcePtr, + DestinationType** aDestPtr) { + return CallQueryInterface(aSourcePtr.get(), aDestPtr); +} + +#endif /* __nsISupportsUtils_h */ diff --git a/xpcom/base/nsIUUIDGenerator.idl b/xpcom/base/nsIUUIDGenerator.idl new file mode 100644 index 0000000000..22087adfa8 --- /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. + */ +[builtinclass, 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 0000000000..462a07dff3 --- /dev/null +++ b/xpcom/base/nsIVersionComparator.idl @@ -0,0 +1,48 @@ +/* -*- 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. + */ +[builtinclass, 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 0000000000..723fa84344 --- /dev/null +++ b/xpcom/base/nsIWeakReference.idl @@ -0,0 +1,114 @@ +/* -*- 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/Attributes.h" +#include "mozilla/MemoryReporting.h" + +// For MOZ_THREAD_SAFETY_OWNERSHIP_CHECKS_SUPPORTED. +#include "nsDebug.h" + +#ifdef MOZ_THREAD_SAFETY_OWNERSHIP_CHECKS_SUPPORTED + +#define MOZ_WEAKREF_DECL_OWNINGTHREAD nsAutoOwningThread _mWeakRefOwningThread; +#define MOZ_WEAKREF_ASSERT_OWNINGTHREAD \ + _mWeakRefOwningThread.AssertOwnership("nsWeakReference not thread-safe") +#define MOZ_WEAKREF_ASSERT_OWNINGTHREAD_DELEGATED(that) \ + (that)->_mWeakRefOwningThread.AssertOwnership("nsWeakReference not thread-safe") + +#else + +#define MOZ_WEAKREF_DECL_OWNINGTHREAD +#define MOZ_WEAKREF_ASSERT_OWNINGTHREAD do { } while (false) +#define MOZ_WEAKREF_ASSERT_OWNINGTHREAD_DELEGATED(that) do { } while (false) + +#endif + +%} + +native MallocSizeOf(mozilla::MallocSizeOf); + +/** + * 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, builtinclass, 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. + */ + [binaryname(QueryReferentFromScript)] + void QueryReferent( in nsIIDRef uuid, [iid_is(uuid), retval] out nsQIResult result ); + + [notxpcom, nostdcall] size_t sizeOfOnlyThis(in MallocSizeOf aMallocSizeOf); +%{C++ + /** + * Returns true if the referring object is alive. Otherwise, false. + */ + bool IsAlive() const + { + return !!mObject; + } + + nsresult QueryReferent(const nsIID& aIID, void** aInstancePtr); + +protected: + friend class nsSupportsWeakReference; + + nsIWeakReference(nsISupports* aObject) + : mObject(aObject) + { + } + + nsIWeakReference() = delete; + + MOZ_WEAKREF_DECL_OWNINGTHREAD + + // The object we're holding a weak reference to. + nsISupports* MOZ_NON_OWNING_REF mObject; +%} + }; + + +/** + * |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(); + }; diff --git a/xpcom/base/nsIWeakReferenceUtils.h b/xpcom/base/nsIWeakReferenceUtils.h new file mode 100644 index 0000000000..b76303096e --- /dev/null +++ b/xpcom/base/nsIWeakReferenceUtils.h @@ -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/. */ + +#ifndef nsIWeakReferenceUtils_h__ +#define nsIWeakReferenceUtils_h__ + +#include "nsCOMPtr.h" +#include "nsIWeakReference.h" + +typedef nsCOMPtr<nsIWeakReference> nsWeakPtr; + +/** + * + */ + +// a type-safe shortcut for calling the |QueryReferent()| member function +// T must inherit from nsIWeakReference, but the cast may be ambiguous. +template <class T, class DestinationType> +inline nsresult CallQueryReferent(T* aSource, DestinationType** aDestination) { + MOZ_ASSERT(aSource, "null parameter"); + MOZ_ASSERT(aDestination, "null parameter"); + + return aSource->QueryReferent(NS_GET_TEMPLATE_IID(DestinationType), + reinterpret_cast<void**>(aDestination)); +} + +inline const nsQueryReferent do_QueryReferent(nsIWeakReference* aRawPtr, + nsresult* aError = 0) { + return nsQueryReferent(aRawPtr, aError); +} + +/** + * Deprecated, use |do_GetWeakReference| instead. + */ +extern nsIWeakReference* NS_GetWeakReference(nsISupports*, + nsresult* aResult = 0); +extern nsIWeakReference* NS_GetWeakReference(nsISupportsWeakReference*, + nsresult* aResult = 0); + +/** + * |do_GetWeakReference| is a convenience function that bundles up all the work + * needed to get a weak reference to an arbitrary object, i.e., the + * |QueryInterface|, test, and call through to |GetWeakReference|, and put it + * into your |nsCOMPtr|. It is specifically designed to cooperate with + * |nsCOMPtr| (or |nsWeakPtr|) like so: |nsWeakPtr myWeakPtr = + * do_GetWeakReference(aPtr);|. + */ +inline already_AddRefed<nsIWeakReference> do_GetWeakReference( + nsISupports* aRawPtr, nsresult* aError = 0) { + return dont_AddRef(NS_GetWeakReference(aRawPtr, aError)); +} + +inline already_AddRefed<nsIWeakReference> do_GetWeakReference( + nsISupportsWeakReference* aRawPtr, nsresult* aError = 0) { + return dont_AddRef(NS_GetWeakReference(aRawPtr, aError)); +} + +inline void do_GetWeakReference(nsIWeakReference* aRawPtr, + nsresult* aError = 0) { + // This signature exists solely to _stop_ you from doing a bad thing. + // Saying |do_GetWeakReference()| on a weak reference itself, + // is very likely to be a programmer error. +} + +template <class T> +inline void do_GetWeakReference(already_AddRefed<T>&) { + // This signature exists solely to _stop_ you from doing the bad thing. + // Saying |do_GetWeakReference()| on a pointer that is not otherwise owned by + // someone else is an automatic leak. See + // <http://bugzilla.mozilla.org/show_bug.cgi?id=8221>. +} + +template <class T> +inline void do_GetWeakReference(already_AddRefed<T>&, nsresult*) { + // This signature exists solely to _stop_ you from doing the bad thing. + // Saying |do_GetWeakReference()| on a pointer that is not otherwise owned by + // someone else is an automatic leak. See + // <http://bugzilla.mozilla.org/show_bug.cgi?id=8221>. +} + +#endif diff --git a/xpcom/base/nsInterfaceRequestorAgg.cpp b/xpcom/base/nsInterfaceRequestorAgg.cpp new file mode 100644 index 0000000000..490341aac2 --- /dev/null +++ b/xpcom/base/nsInterfaceRequestorAgg.cpp @@ -0,0 +1,74 @@ +/* -*- 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 = mozilla::GetCurrentSerialEventTarget(); + } + } + + 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("nsInterfaceRequestorAgg::mFirst", mConsumerTarget, + mFirst.forget()); + NS_ProxyRelease("nsInterfaceRequestorAgg::mSecond", mConsumerTarget, + mSecond.forget()); +} + +nsresult NS_NewInterfaceRequestorAggregation(nsIInterfaceRequestor* aFirst, + nsIInterfaceRequestor* aSecond, + nsIInterfaceRequestor** aResult) { + *aResult = new nsInterfaceRequestorAgg(aFirst, aSecond); + + NS_ADDREF(*aResult); + return NS_OK; +} + +nsresult NS_NewInterfaceRequestorAggregation(nsIInterfaceRequestor* aFirst, + nsIInterfaceRequestor* aSecond, + nsIEventTarget* aTarget, + nsIInterfaceRequestor** aResult) { + *aResult = new nsInterfaceRequestorAgg(aFirst, aSecond, aTarget); + + NS_ADDREF(*aResult); + return NS_OK; +} diff --git a/xpcom/base/nsInterfaceRequestorAgg.h b/xpcom/base/nsInterfaceRequestorAgg.h new file mode 100644 index 0000000000..06c6b3d3a3 --- /dev/null +++ b/xpcom/base/nsInterfaceRequestorAgg.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 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/nsMacPreferencesReader.h b/xpcom/base/nsMacPreferencesReader.h new file mode 100644 index 0000000000..40d5af553a --- /dev/null +++ b/xpcom/base/nsMacPreferencesReader.h @@ -0,0 +1,34 @@ +/* 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 MacPreferencesReader_h__ +#define MacPreferencesReader_h__ + +//----------------------------------------------------------------------------- + +#include "nsIMacPreferencesReader.h" + +#define NS_MACPREFERENCESREADER_CID \ + { \ + 0xb0f20595, 0x88ce, 0x4738, { \ + 0xa1, 0xa4, 0x24, 0xde, 0x78, 0xeb, 0x80, 0x51 \ + } \ + } +#define NS_MACPREFERENCESREADER_CONTRACTID \ + "@mozilla.org/mac-preferences-reader;1" + +//----------------------------------------------------------------------------- + +class nsMacPreferencesReader : public nsIMacPreferencesReader { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIMACPREFERENCESREADER + + nsMacPreferencesReader(){}; + + protected: + virtual ~nsMacPreferencesReader() = default; +}; + +#endif // MacPreferencesReader_h__ diff --git a/xpcom/base/nsMacPreferencesReader.mm b/xpcom/base/nsMacPreferencesReader.mm new file mode 100644 index 0000000000..bfec81cd85 --- /dev/null +++ b/xpcom/base/nsMacPreferencesReader.mm @@ -0,0 +1,87 @@ +/* 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 "MacStringHelpers.h" +#include "nsMacPreferencesReader.h" +#include "nsString.h" + +#include "js/JSON.h" +#include "js/RootingAPI.h" +#include "js/Value.h" +#include "mozilla/JSONStringWriteFuncs.h" + +NS_IMPL_ISUPPORTS(nsMacPreferencesReader, nsIMacPreferencesReader) + +using namespace mozilla; + +static void EvaluateDict(JSONWriter* aWriter, + NSDictionary<NSString*, id>* aDict); + +static void EvaluateArray(JSONWriter* aWriter, NSArray* aArray) { + for (id elem in aArray) { + if ([elem isKindOfClass:[NSString class]]) { + aWriter->StringElement(MakeStringSpan([elem UTF8String])); + } else if ([elem isKindOfClass:[NSNumber class]]) { + aWriter->IntElement([elem longLongValue]); + } else if ([elem isKindOfClass:[NSArray class]]) { + aWriter->StartArrayElement(); + EvaluateArray(aWriter, elem); + aWriter->EndArray(); + } else if ([elem isKindOfClass:[NSDictionary class]]) { + aWriter->StartObjectElement(); + EvaluateDict(aWriter, elem); + aWriter->EndObject(); + } + } +} + +static void EvaluateDict(JSONWriter* aWriter, + NSDictionary<NSString*, id>* aDict) { + for (NSString* key in aDict) { + id value = aDict[key]; + if ([value isKindOfClass:[NSString class]]) { + aWriter->StringProperty(MakeStringSpan([key UTF8String]), + MakeStringSpan([value UTF8String])); + } else if ([value isKindOfClass:[NSNumber class]]) { + aWriter->IntProperty(MakeStringSpan([key UTF8String]), + [value longLongValue]); + } else if ([value isKindOfClass:[NSArray class]]) { + aWriter->StartArrayProperty(MakeStringSpan([key UTF8String])); + EvaluateArray(aWriter, value); + aWriter->EndArray(); + } else if ([value isKindOfClass:[NSDictionary class]]) { + aWriter->StartObjectProperty(MakeStringSpan([key UTF8String])); + EvaluateDict(aWriter, value); + aWriter->EndObject(); + } + } +} + +NS_IMETHODIMP +nsMacPreferencesReader::PoliciesEnabled(bool* aPoliciesEnabled) { + NSString* policiesEnabledStr = + [NSString stringWithUTF8String:ENTERPRISE_POLICIES_ENABLED_KEY]; + *aPoliciesEnabled = [[NSUserDefaults standardUserDefaults] + boolForKey:policiesEnabledStr] == YES; + return NS_OK; +} + +NS_IMETHODIMP +nsMacPreferencesReader::ReadPreferences(JSContext* aCx, + JS::MutableHandle<JS::Value> aResult) { + JSONStringWriteFunc<nsAutoCString> jsonStr; + JSONWriter w(jsonStr); + w.Start(); + EvaluateDict( + &w, [[NSUserDefaults standardUserDefaults] dictionaryRepresentation]); + w.End(); + + NS_ConvertUTF8toUTF16 jsonStr16(jsonStr.StringCRef()); + + JS::RootedValue val(aCx); + MOZ_ALWAYS_TRUE(JS_ParseJSON(aCx, jsonStr16.get(), jsonStr16.Length(), &val)); + + aResult.set(val); + return NS_OK; +} diff --git a/xpcom/base/nsMacUtilsImpl.cpp b/xpcom/base/nsMacUtilsImpl.cpp new file mode 100644 index 0000000000..86227b7aca --- /dev/null +++ b/xpcom/base/nsMacUtilsImpl.cpp @@ -0,0 +1,544 @@ +/* -*- 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 "base/command_line.h" +#include "base/process_util.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/Omnijar.h" +#include "nsDirectoryServiceDefs.h" +#include "nsCOMPtr.h" +#include "nsComponentManagerUtils.h" +#include "nsIFile.h" +#include "nsServiceManagerUtils.h" +#include "nsThreadUtils.h" +#include "nsXULAppAPI.h" +#include "prenv.h" + +#if defined(MOZ_SANDBOX) +# include "mozilla/SandboxSettings.h" +#endif + +#include <CoreFoundation/CoreFoundation.h> +#include <CoreServices/CoreServices.h> +#if defined(__aarch64__) +# include <dlfcn.h> +#endif +#include <sys/sysctl.h> + +using mozilla::StaticMutexAutoLock; +using mozilla::Unused; + +#if defined(MOZ_SANDBOX) || defined(__aarch64__) +// For thread safe setting/checking of sCachedAppPath +static StaticMutex sCachedAppPathMutex; + +// Cache the appDir returned from GetAppPath to avoid doing I/O +static StaticAutoPtr<nsCString> sCachedAppPath + MOZ_GUARDED_BY(sCachedAppPathMutex); +#endif + +// The cached machine architectures of the .app bundle which can +// be multiple architectures for universal binaries. +static std::atomic<uint32_t> sBundleArchMaskAtomic = 0; + +#if defined(__aarch64__) +// Limit XUL translation to one attempt +static std::atomic<bool> sIsXULTranslated = false; +#endif + +// Info.plist key associated with the developer repo path +#define MAC_DEV_REPO_KEY "MozillaDeveloperRepoPath" +// Info.plist key associated with the developer repo object directory +#define MAC_DEV_OBJ_KEY "MozillaDeveloperObjPath" + +// Workaround this constant not being available in the macOS SDK +#define kCFBundleExecutableArchitectureARM64 0x0100000c + +enum TCSMStatus { TCSM_Unknown = 0, TCSM_Available, TCSM_Unavailable }; + +// Initialize with Unknown until we've checked if TCSM is available to set +static Atomic<TCSMStatus> sTCSMStatus(TCSM_Unknown); + +#if defined(MOZ_SANDBOX) || defined(__aarch64__) + +// Utility method to call ClearOnShutdown() on the main thread +static nsresult ClearCachedAppPathOnShutdown() { + MOZ_ASSERT(NS_IsMainThread()); + ClearOnShutdown(&sCachedAppPath); + return NS_OK; +} + +// Get the path to the .app directory (aka bundle) for the parent process. +// When executing in the child process, this is the outer .app (such as +// Firefox.app) and not the inner .app containing the child process +// executable. We don't rely on the actual .app extension to allow for the +// bundle being renamed. +bool nsMacUtilsImpl::GetAppPath(nsCString& aAppPath) { + StaticMutexAutoLock lock(sCachedAppPathMutex); + if (sCachedAppPath) { + aAppPath.Assign(*sCachedAppPath); + return true; + } + + nsAutoCString appPath; + nsAutoCString appBinaryPath( + (CommandLine::ForCurrentProcess()->argv()[0]).c_str()); + + // The binary path resides within the .app dir in Contents/MacOS, + // e.g., Firefox.app/Contents/MacOS/firefox. Search backwards in + // the binary path for the end of .app path. + auto pattern = "/Contents/MacOS/"_ns; + nsAutoCString::const_iterator start, end; + appBinaryPath.BeginReading(start); + appBinaryPath.EndReading(end); + if (RFindInReadable(pattern, start, end)) { + end = start; + appBinaryPath.BeginReading(start); + + // If we're executing in a child process, get the parent .app path + // by searching backwards once more. The child executable resides + // in Firefox.app/Contents/MacOS/plugin-container/Contents/MacOS. + if (!XRE_IsParentProcess()) { + if (RFindInReadable(pattern, start, end)) { + end = start; + appBinaryPath.BeginReading(start); + } else { + return false; + } + } + + appPath.Assign(Substring(start, end)); + } else { + return false; + } + + nsCOMPtr<nsIFile> app; + nsresult rv = NS_NewLocalFile(NS_ConvertUTF8toUTF16(appPath), true, + getter_AddRefs(app)); + if (NS_FAILED(rv)) { + return false; + } + + rv = app->Normalize(); + if (NS_FAILED(rv)) { + return false; + } + app->GetNativePath(aAppPath); + + if (!sCachedAppPath) { + sCachedAppPath = new nsCString(aAppPath); + + if (NS_IsMainThread()) { + ClearCachedAppPathOnShutdown(); + } else { + NS_DispatchToMainThread( + NS_NewRunnableFunction("ClearCachedAppPathOnShutdown", + [] { ClearCachedAppPathOnShutdown(); })); + } + } + + return true; +} + +#endif /* MOZ_SANDBOX || __aarch64__ */ + +#if defined(MOZ_SANDBOX) && defined(DEBUG) +// If XPCOM_MEM_BLOAT_LOG or XPCOM_MEM_LEAK_LOG is set to a log file +// path, return the path to the parent directory (where sibling log +// files will be saved.) +nsresult nsMacUtilsImpl::GetBloatLogDir(nsCString& aDirectoryPath) { + nsAutoCString bloatLog(PR_GetEnv("XPCOM_MEM_BLOAT_LOG")); + if (bloatLog.IsEmpty()) { + bloatLog = PR_GetEnv("XPCOM_MEM_LEAK_LOG"); + } + if (!bloatLog.IsEmpty() && bloatLog != "1" && bloatLog != "2") { + return GetDirectoryPath(bloatLog.get(), aDirectoryPath); + } + return NS_OK; +} + +// Given a path to a file, return the directory which contains it. +nsresult nsMacUtilsImpl::GetDirectoryPath(const char* aPath, + nsCString& aDirectoryPath) { + nsresult rv = NS_OK; + nsCOMPtr<nsIFile> file = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = file->InitWithNativePath(nsDependentCString(aPath)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> directoryFile; + rv = file->GetParent(getter_AddRefs(directoryFile)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = directoryFile->Normalize(); + NS_ENSURE_SUCCESS(rv, rv); + + if (NS_FAILED(directoryFile->GetNativePath(aDirectoryPath))) { + MOZ_CRASH("Failed to get path for an nsIFile"); + } + return NS_OK; +} +#endif /* MOZ_SANDBOX && DEBUG */ + +/* static */ +bool nsMacUtilsImpl::IsTCSMAvailable() { + if (sTCSMStatus == TCSM_Unknown) { + uint32_t oldVal = 0; + size_t oldValSize = sizeof(oldVal); + int rv = sysctlbyname("kern.tcsm_available", &oldVal, &oldValSize, NULL, 0); + TCSMStatus newStatus; + if (rv < 0 || oldVal == 0) { + newStatus = TCSM_Unavailable; + } else { + newStatus = TCSM_Available; + } + // The value of sysctl kern.tcsm_available is the same for all + // threads within the same process. If another thread raced with us + // and initialized sTCSMStatus first (changing it from + // TCSM_Unknown), we can continue without needing to update it + // again. Hence, we ignore compareExchange's return value. + Unused << sTCSMStatus.compareExchange(TCSM_Unknown, newStatus); + } + return (sTCSMStatus == TCSM_Available); +} + +static nsresult EnableTCSM() { + uint32_t newVal = 1; + int rv = sysctlbyname("kern.tcsm_enable", NULL, 0, &newVal, sizeof(newVal)); + if (rv < 0) { + return NS_ERROR_UNEXPECTED; + } + return NS_OK; +} + +#if defined(DEBUG) +static bool IsTCSMEnabled() { + uint32_t oldVal = 0; + size_t oldValSize = sizeof(oldVal); + int rv = sysctlbyname("kern.tcsm_enable", &oldVal, &oldValSize, NULL, 0); + return (rv == 0) && (oldVal != 0); +} +#endif + +/* + * Intentionally return void so that failures will be ignored in non-debug + * builds. This method uses new sysctls which may not be as thoroughly tested + * and we don't want to cause crashes handling the failure due to an OS bug. + */ +/* static */ +void nsMacUtilsImpl::EnableTCSMIfAvailable() { + if (IsTCSMAvailable()) { + if (NS_FAILED(EnableTCSM())) { + NS_WARNING("Failed to enable TCSM"); + } + MOZ_ASSERT(IsTCSMEnabled()); + } +} + +// Returns 0 on error. +/* static */ +uint32_t nsMacUtilsImpl::GetPhysicalCPUCount() { + uint32_t oldVal = 0; + size_t oldValSize = sizeof(oldVal); + int rv = sysctlbyname("hw.physicalcpu_max", &oldVal, &oldValSize, NULL, 0); + if (rv == -1) { + return 0; + } + return oldVal; +} + +/* + * Helper function to read a string value for a given key from the .app's + * Info.plist. + */ +static nsresult GetStringValueFromBundlePlist(const nsAString& aKey, + nsAutoCString& aValue) { + CFBundleRef mainBundle = CFBundleGetMainBundle(); + if (mainBundle == nullptr) { + return NS_ERROR_FAILURE; + } + + // Read this app's bundle Info.plist as a dictionary + CFDictionaryRef bundleInfoDict = CFBundleGetInfoDictionary(mainBundle); + if (bundleInfoDict == nullptr) { + return NS_ERROR_FAILURE; + } + + nsAutoCString keyAutoCString = NS_ConvertUTF16toUTF8(aKey); + CFStringRef key = CFStringCreateWithCString( + kCFAllocatorDefault, keyAutoCString.get(), kCFStringEncodingUTF8); + if (key == nullptr) { + return NS_ERROR_FAILURE; + } + + CFStringRef value = (CFStringRef)CFDictionaryGetValue(bundleInfoDict, key); + CFRelease(key); + if (value == nullptr) { + return NS_ERROR_FAILURE; + } + + CFIndex valueLength = CFStringGetLength(value); + if (valueLength == 0) { + return NS_ERROR_FAILURE; + } + + const char* valueCString = + CFStringGetCStringPtr(value, kCFStringEncodingUTF8); + if (valueCString) { + aValue.Assign(valueCString); + return NS_OK; + } + + CFIndex maxLength = + CFStringGetMaximumSizeForEncoding(valueLength, kCFStringEncodingUTF8) + 1; + char* valueBuffer = static_cast<char*>(moz_xmalloc(maxLength)); + + if (!CFStringGetCString(value, valueBuffer, maxLength, + kCFStringEncodingUTF8)) { + free(valueBuffer); + return NS_ERROR_FAILURE; + } + + aValue.Assign(valueBuffer); + free(valueBuffer); + return NS_OK; +} + +/* + * Helper function for reading a path string from the .app's Info.plist + * and returning a directory object for that path with symlinks resolved. + */ +static nsresult GetDirFromBundlePlist(const nsAString& aKey, nsIFile** aDir) { + nsresult rv; + + nsAutoCString dirPath; + rv = GetStringValueFromBundlePlist(aKey, dirPath); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> dir; + rv = NS_NewLocalFile(NS_ConvertUTF8toUTF16(dirPath), false, + getter_AddRefs(dir)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = dir->Normalize(); + NS_ENSURE_SUCCESS(rv, rv); + + bool isDirectory = false; + rv = dir->IsDirectory(&isDirectory); + NS_ENSURE_SUCCESS(rv, rv); + if (!isDirectory) { + return NS_ERROR_FILE_NOT_DIRECTORY; + } + + dir.swap(*aDir); + return NS_OK; +} + +nsresult nsMacUtilsImpl::GetRepoDir(nsIFile** aRepoDir) { +#if defined(MOZ_SANDBOX) + MOZ_ASSERT(!mozilla::IsPackagedBuild()); +#endif + return GetDirFromBundlePlist(NS_LITERAL_STRING_FROM_CSTRING(MAC_DEV_REPO_KEY), + aRepoDir); +} + +nsresult nsMacUtilsImpl::GetObjDir(nsIFile** aObjDir) { +#if defined(MOZ_SANDBOX) + MOZ_ASSERT(!mozilla::IsPackagedBuild()); +#endif + return GetDirFromBundlePlist(NS_LITERAL_STRING_FROM_CSTRING(MAC_DEV_OBJ_KEY), + aObjDir); +} + +/* static */ +nsresult nsMacUtilsImpl::GetArchitecturesForBundle(uint32_t* aArchMask) { + MOZ_ASSERT(aArchMask); + + *aArchMask = sBundleArchMaskAtomic; + if (*aArchMask != 0) { + return NS_OK; + } + + 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) { + *aArchMask |= base::PROCESS_ARCH_PPC; + } else if (archInt == kCFBundleExecutableArchitectureI386) { + *aArchMask |= base::PROCESS_ARCH_I386; + } else if (archInt == kCFBundleExecutableArchitecturePPC64) { + *aArchMask |= base::PROCESS_ARCH_PPC_64; + } else if (archInt == kCFBundleExecutableArchitectureX86_64) { + *aArchMask |= base::PROCESS_ARCH_X86_64; + } else if (archInt == kCFBundleExecutableArchitectureARM64) { + *aArchMask |= base::PROCESS_ARCH_ARM_64; + } + } + + ::CFRelease(archList); + + sBundleArchMaskAtomic = *aArchMask; + + return NS_OK; +} + +/* static */ +nsresult nsMacUtilsImpl::GetArchitecturesForBinary(const char* aPath, + uint32_t* aArchMask) { + MOZ_ASSERT(aArchMask); + + *aArchMask = 0; + + CFURLRef url = ::CFURLCreateFromFileSystemRepresentation( + kCFAllocatorDefault, (const UInt8*)aPath, strlen(aPath), false); + if (!url) { + return NS_ERROR_FAILURE; + } + + CFArrayRef archs = ::CFBundleCopyExecutableArchitecturesForURL(url); + if (!archs) { + CFRelease(url); + return NS_ERROR_FAILURE; + } + + CFIndex archCount = ::CFArrayGetCount(archs); + for (CFIndex i = 0; i < archCount; i++) { + CFNumberRef currentArch = + static_cast<CFNumberRef>(::CFArrayGetValueAtIndex(archs, i)); + int currentArchInt = 0; + if (!::CFNumberGetValue(currentArch, kCFNumberIntType, ¤tArchInt)) { + continue; + } + switch (currentArchInt) { + case kCFBundleExecutableArchitectureX86_64: + *aArchMask |= base::PROCESS_ARCH_X86_64; + break; + case kCFBundleExecutableArchitectureARM64: + *aArchMask |= base::PROCESS_ARCH_ARM_64; + break; + default: + break; + } + } + + CFRelease(url); + CFRelease(archs); + + // We expect x86 or ARM64 or both. + if (*aArchMask == 0) { + return NS_ERROR_UNEXPECTED; + } + + return NS_OK; +} + +#if defined(__aarch64__) +// Pre-translate XUL so that x64 child processes launched after this +// translation will not incur the translation overhead delaying startup. +// Returns 1 if translation is in progress, -1 on an error encountered before +// translation, and otherwise returns the result of rosetta_translate_binaries. +/* static */ +int nsMacUtilsImpl::PreTranslateXUL() { + bool expected = false; + if (!sIsXULTranslated.compare_exchange_strong(expected, true)) { + // Translation is already done or in progress. + return 1; + } + + // Get the path to XUL by first getting the + // outer .app path and appending the path to XUL. + nsCString xulPath; + if (!GetAppPath(xulPath)) { + return -1; + } + xulPath.Append("/Contents/MacOS/XUL"); + + return PreTranslateBinary(xulPath); +} + +// Use Chromium's method to pre-translate the provided binary using the +// undocumented function "rosetta_translate_binaries" from libRosetta.dylib. +// Re-translating the same binary does not cause translation to occur again. +// Returns -1 on an error encountered before translation, otherwise returns +// the rosetta_translate_binaries result. This method is partly copied from +// Chromium code. +/* static */ +int nsMacUtilsImpl::PreTranslateBinary(nsCString aBinaryPath) { + // Do not attempt to use this in child processes. Child + // processes executing should already be translated and + // sandboxing may interfere with translation. + MOZ_ASSERT(XRE_IsParentProcess()); + if (!XRE_IsParentProcess()) { + return -1; + } + + // Translation can take several seconds and therefore + // should not be done on the main thread. + MOZ_ASSERT(!NS_IsMainThread()); + if (NS_IsMainThread()) { + return -1; + } + + // @available() is not available for macOS 11 at this time so use + // -Wunguarded-availability-new to avoid compiler warnings caused + // by an earlier minimum SDK. ARM64 builds require the 11.0 SDK and + // can not be run on earlier OS versions so this is not a concern. +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunguarded-availability-new" + // If Rosetta is not installed, do not proceed. + if (!CFBundleIsArchitectureLoadable(CPU_TYPE_X86_64)) { + return -1; + } +# pragma clang diagnostic pop + + if (aBinaryPath.IsEmpty()) { + return -1; + } + + // int rosetta_translate_binaries(const char*[] paths, int npaths) + using rosetta_translate_binaries_t = int (*)(const char*[], int); + + static auto rosetta_translate_binaries = []() { + void* libRosetta = + dlopen("/usr/lib/libRosetta.dylib", RTLD_LAZY | RTLD_LOCAL); + if (!libRosetta) { + return static_cast<rosetta_translate_binaries_t>(nullptr); + } + + return reinterpret_cast<rosetta_translate_binaries_t>( + dlsym(libRosetta, "rosetta_translate_binaries")); + }(); + + if (!rosetta_translate_binaries) { + return -1; + } + + const char* pathPtr = aBinaryPath.get(); + return rosetta_translate_binaries(&pathPtr, 1); +} + +#endif diff --git a/xpcom/base/nsMacUtilsImpl.h b/xpcom/base/nsMacUtilsImpl.h new file mode 100644 index 0000000000..9a20b539d1 --- /dev/null +++ b/xpcom/base/nsMacUtilsImpl.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 nsMacUtilsImpl_h___ +#define nsMacUtilsImpl_h___ + +#include "nsString.h" +#include "mozilla/Atomics.h" +#include "mozilla/Attributes.h" +#include "mozilla/StaticMutex.h" +#include "mozilla/StaticPtr.h" + +using mozilla::Atomic; +using mozilla::StaticAutoPtr; +using mozilla::StaticMutex; + +class nsIFile; + +namespace nsMacUtilsImpl { + +// Return the repo directory and the repo object directory respectively. +// These should only be used on Mac developer builds to determine the path +// to the repo or object directory. +nsresult GetRepoDir(nsIFile** aRepoDir); +nsresult GetObjDir(nsIFile** aObjDir); + +#if defined(MOZ_SANDBOX) || defined(__aarch64__) +bool GetAppPath(nsCString& aAppPath); +#endif /* MOZ_SANDBOX || __aarch64__ */ + +#if defined(MOZ_SANDBOX) && defined(DEBUG) +nsresult GetBloatLogDir(nsCString& aDirectoryPath); +nsresult GetDirectoryPath(const char* aPath, nsCString& aDirectoryPath); +#endif /* MOZ_SANDBOX && DEBUG */ + +void EnableTCSMIfAvailable(); +bool IsTCSMAvailable(); +uint32_t GetPhysicalCPUCount(); +nsresult GetArchitecturesForBundle(uint32_t* aArchMask); +nsresult GetArchitecturesForBinary(const char* aPath, uint32_t* aArchMask); + +#if defined(__aarch64__) +// Pre-translate binaries to avoid translation delays when launching +// x64 child process instances for the first time. i.e. on first launch +// after installation or after an update. Translations are cached so +// repeated launches of the binaries do not encounter delays. +int PreTranslateXUL(); +int PreTranslateBinary(nsCString aBinaryPath); +#endif +} // namespace nsMacUtilsImpl + +#endif /* nsMacUtilsImpl_h___ */ diff --git a/xpcom/base/nsMaybeWeakPtr.h b/xpcom/base/nsMaybeWeakPtr.h new file mode 100644 index 0000000000..0566624265 --- /dev/null +++ b/xpcom/base/nsMaybeWeakPtr.h @@ -0,0 +1,169 @@ +/* -*- Mode: C++; tab-width: 8; 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/. */ + +#ifndef nsMaybeWeakPtr_h_ +#define nsMaybeWeakPtr_h_ + +#include "mozilla/Attributes.h" +#include "mozilla/Try.h" +#include "nsCOMPtr.h" +#include "nsIWeakReferenceUtils.h" +#include "nsTArray.h" +#include "nsCycleCollectionNoteChild.h" + +// nsMaybeWeakPtr is a helper object to hold a strong-or-weak reference +// to the template class. It's pretty minimal, but sufficient. + +template <class T> +class nsMaybeWeakPtr { + public: + nsMaybeWeakPtr() = default; + MOZ_IMPLICIT nsMaybeWeakPtr(T* aRef) : mPtr(aRef), mWeak(false) {} + MOZ_IMPLICIT nsMaybeWeakPtr(const nsCOMPtr<nsIWeakReference>& aRef) + : mPtr(aRef), mWeak(true) {} + + nsMaybeWeakPtr<T>& operator=(T* aRef) { + mPtr = aRef; + mWeak = false; + return *this; + } + + nsMaybeWeakPtr<T>& operator=(const nsCOMPtr<nsIWeakReference>& aRef) { + mPtr = aRef; + mWeak = true; + return *this; + } + + bool operator==(const nsMaybeWeakPtr<T>& other) const { + return mPtr == other.mPtr; + } + + nsISupports* GetRawValue() const { return mPtr.get(); } + bool IsWeak() const { return mWeak; } + + const nsCOMPtr<T> GetValue() const; + + private: + nsCOMPtr<nsISupports> mPtr; + bool mWeak; +}; + +// nsMaybeWeakPtrArray is an array of MaybeWeakPtr objects, that knows how to +// grab a weak reference to a given object if requested. It only allows a +// given object to appear in the array once. + +template <class T> +class nsMaybeWeakPtrArray : public CopyableTArray<nsMaybeWeakPtr<T>> { + typedef nsTArray<nsMaybeWeakPtr<T>> MaybeWeakArray; + + nsresult SetMaybeWeakPtr(nsMaybeWeakPtr<T>& aRef, T* aElement, + bool aOwnsWeak) { + nsresult rv = NS_OK; + + if (aOwnsWeak) { + aRef = do_GetWeakReference(aElement, &rv); + } else { + aRef = aElement; + } + + return rv; + } + + public: + nsresult AppendWeakElement(T* aElement, bool aOwnsWeak) { + nsMaybeWeakPtr<T> ref; + MOZ_TRY(SetMaybeWeakPtr(ref, aElement, aOwnsWeak)); + + MaybeWeakArray::AppendElement(ref); + return NS_OK; + } + + nsresult AppendWeakElementUnlessExists(T* aElement, bool aOwnsWeak) { + nsMaybeWeakPtr<T> ref; + MOZ_TRY(SetMaybeWeakPtr(ref, aElement, aOwnsWeak)); + + if (MaybeWeakArray::Contains(ref)) { + return NS_ERROR_INVALID_ARG; + } + + MaybeWeakArray::AppendElement(ref); + return NS_OK; + } + + nsresult RemoveWeakElement(T* aElement) { + if (MaybeWeakArray::RemoveElement(aElement)) { + return NS_OK; + } + + // Don't use do_GetWeakReference; it should only be called if we know + // the object supports weak references. + nsCOMPtr<nsISupportsWeakReference> supWeakRef = do_QueryInterface(aElement); + if (!supWeakRef) { + return NS_ERROR_INVALID_ARG; + } + + nsCOMPtr<nsIWeakReference> weakRef; + nsresult rv = supWeakRef->GetWeakReference(getter_AddRefs(weakRef)); + NS_ENSURE_SUCCESS(rv, rv); + + if (MaybeWeakArray::RemoveElement(weakRef)) { + return NS_OK; + } + + return NS_ERROR_INVALID_ARG; + } +}; + +template <class T> +const nsCOMPtr<T> nsMaybeWeakPtr<T>::GetValue() const { + if (!mPtr) { + return nullptr; + } + + nsCOMPtr<T> ref; + nsresult rv; + + if (mWeak) { + nsCOMPtr<nsIWeakReference> weakRef = do_QueryInterface(mPtr); + if (NS_WARN_IF(!weakRef)) { + return nullptr; + } + ref = do_QueryReferent(weakRef, &rv); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv) || rv == NS_ERROR_NULL_POINTER, + "QueryReferent failed with non-null pointer"); + } else { + ref = do_QueryInterface(mPtr, &rv); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "QueryInterface failed with non-null pointer"); + } + return ref; +} + +template <typename T> +inline void ImplCycleCollectionUnlink(nsMaybeWeakPtrArray<T>& aField) { + aField.Clear(); +} + +template <typename E> +inline void ImplCycleCollectionTraverse( + nsCycleCollectionTraversalCallback& aCallback, + nsMaybeWeakPtrArray<E>& aField, const char* aName, uint32_t aFlags = 0) { + aFlags |= CycleCollectionEdgeNameArrayFlag; + size_t length = aField.Length(); + for (size_t i = 0; i < length; ++i) { + CycleCollectionNoteChild(aCallback, aField[i].GetRawValue(), aName, aFlags); + } +} + +// Call a method on each element in the array, but only if the element is +// non-null. + +#define ENUMERATE_WEAKARRAY(array, type, method) \ + for (uint32_t array_idx = 0; array_idx < array.Length(); ++array_idx) { \ + const nsCOMPtr<type>& e = array.ElementAt(array_idx).GetValue(); \ + if (e) e->method; \ + } + +#endif diff --git a/xpcom/base/nsMemory.h b/xpcom/base/nsMemory.h new file mode 100644 index 0000000000..7fda7ae837 --- /dev/null +++ b/xpcom/base/nsMemory.h @@ -0,0 +1,62 @@ +/* -*- 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 nsMemory_h__ +#define nsMemory_h__ + +#include "nsError.h" + +/** + * + * 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 notifications + * 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. + * + * "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. + */ + +// This is implemented in nsMemoryImpl.cpp. + +namespace nsMemory { + +/** + * 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 and the call + * was not on the application's main thread. + */ +nsresult HeapMinimize(bool aImmediate); + +/** + * 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. + */ +bool IsLowMemoryPlatform(); +} // namespace nsMemory + +#endif // nsMemory_h__ diff --git a/xpcom/base/nsMemoryImpl.cpp b/xpcom/base/nsMemoryImpl.cpp new file mode 100644 index 0000000000..4996d27c7a --- /dev/null +++ b/xpcom/base/nsMemoryImpl.cpp @@ -0,0 +1,131 @@ +/* -*- 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 "nsMemory.h" +#include "nsThreadUtils.h" + +#include "nsIObserver.h" +#include "nsIObserverService.h" +#include "nsIRunnable.h" +#include "nsISimpleEnumerator.h" + +#include "nsCOMPtr.h" +#include "mozilla/Services.h" +#include "mozilla/Atomics.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 HardwareUtils.java +# define LOW_MEMORY_THRESHOLD_KB (384 * 1024) +#endif + +static mozilla::Atomic<bool> sIsFlushing; +static PRIntervalTime sLastFlushTime = 0; + +// static +bool nsMemory::IsLowMemoryPlatform() { +#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 + + // check if MemTotal from /proc/meminfo is less than LOW_MEMORY_THRESHOLD_KB + FILE* fd = fopen("/proc/meminfo", "r"); + if (!fd) { + return false; + } + uint64_t mem = 0; + int rv = fscanf(fd, "MemTotal: %" PRIu64 " kB", &mem); + if (fclose(fd)) { + return false; + } + if (rv != 1) { + return false; + } + sLowMemory = (mem < LOW_MEMORY_THRESHOLD_KB) ? 1 : 0; + } + return (sLowMemory == 1); +#else + return false; +#endif +} + +static void 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; +} + +static nsresult FlushMemory(const char16_t* aReason, bool aImmediate) { + 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 and run 'em asynchronously. + nsresult rv = NS_OK; + if (aImmediate) { + RunFlushers(aReason); + } else { + // Don't broadcast more than once every 1000ms to avoid being noisy + if (PR_IntervalToMicroseconds(now - sLastFlushTime) > 1000) { + nsCOMPtr<nsIRunnable> runnable(NS_NewRunnableFunction( + "FlushMemory", + [reason = aReason]() -> void { RunFlushers(reason); })); + NS_DispatchToMainThread(runnable.forget()); + } + } + + sLastFlushTime = now; + return rv; +} + +nsresult nsMemory::HeapMinimize(bool aImmediate) { + return FlushMemory(u"heap-minimize", aImmediate); +} diff --git a/xpcom/base/nsMemoryInfoDumper.cpp b/xpcom/base/nsMemoryInfoDumper.cpp new file mode 100644 index 0000000000..28af408178 --- /dev/null +++ b/xpcom/base/nsMemoryInfoDumper.cpp @@ -0,0 +1,742 @@ +/* -*- 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 "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 + +// Some Android devices seem to send RT signals to Firefox so we want to avoid +// consuming those as they're not user triggered. +#if !defined(ANDROID) && (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) + : mozilla::Runnable("DumpMemoryInfoToTempDirRunnable"), + 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) + : mozilla::Runnable("GCAndCCLogDumpRunnable"), + 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() = default; + + 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 = */ u""_ns, + /* 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 = */ u""_ns, + /* 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 = */ u""_ns, + /* 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 = */ u""_ns, 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("memory report"_ns, doMemoryReport); + fw->RegisterCallback("minimize memory report"_ns, doMemoryReport); + // Dump GC and CC logs (from the main thread). + fw->RegisterCallback("gc log"_ns, doGCCCDump); + fw->RegisterCallback("abbreviated gc log"_ns, 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); + } +} + +} // namespace +#endif // MOZ_SUPPORTS_FIFO } + +NS_IMPL_ISUPPORTS(nsMemoryInfoDumper, nsIMemoryInfoDumper) + +nsMemoryInfoDumper::nsMemoryInfoDumper() = default; + +nsMemoryInfoDumper::~nsMemoryInfoDumper() = default; + +/* 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); + } +#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 = nsCycleCollector_createLogger(); + + 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(CCReason::DUMP_HEAP, 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 = nsCycleCollector_createLogger(); + + if (aDumpAllTraces) { + nsCOMPtr<nsICycleCollectorListener> allTracesLogger; + logger->AllTraces(getter_AddRefs(allTracesLogger)); + logger = allTracesLogger; + } + + logger->SetLogSink(aSink); + + nsJSContext::CycleCollectNow(CCReason::DUMP_HEAP, 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 final : public JSONWriteFunc { + public: + explicit GZWriterWrapper(nsGZFileWriter* aGZWriter) : mGZWriter(aGZWriter) {} + + void Write(const Span<const char>& aStr) final { + // Ignore any failure because JSONWriteFunc doesn't have a mechanism for + // handling errors. + Unused << mGZWriter->Write(aStr.data(), aStr.size()); + } + + 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(std::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". + // + // `appendAboutMemoryMain()` in aboutMemory.js does much the same thing + // for live memory reports. + 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); + mWriter->StringProperty("path", PromiseFlatCString(aPath)); + mWriter->IntProperty("kind", aKind); + mWriter->IntProperty("units", aUnits); + mWriter->IntProperty("amount", aAmount); + mWriter->StringProperty("description", PromiseFlatCString(aDescription)); + } + 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() = default; + + 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("memory-reports"_ns); + 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 = u"nsIMemoryInfoDumper dumped reports to "_ns; + msg.Append(path); + return cs->LogStringMessage(msg.get()); + } + + private: + ~TempDirFinishCallback() = default; + + 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( + std::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, + bool aMinimizeMemoryUsage) { + 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; + return DumpMemoryInfoToFile(reportsFile, aFinishDumping, aFinishDumpingData, + aAnonymize, aMinimizeMemoryUsage, 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("incomplete-"_ns + reportsFinalFilename, + getter_AddRefs(reportsTmpFile), + "memory-reports"_ns); + 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), + "memory-reports"_ns); + 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. + dmd::StatusMsg("opened %s for writing\n", dmdFile->HumanReadablePath().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 0000000000..68f6d46de7 --- /dev/null +++ b/xpcom/base/nsMemoryInfoDumper.h @@ -0,0 +1,48 @@ +/* -*- 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 0000000000..aaf1baeea2 --- /dev/null +++ b/xpcom/base/nsMemoryReporterManager.cpp @@ -0,0 +1,2923 @@ +/* -*- 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 "nsMemoryReporterManager.h" + +#include "nsAtomTable.h" +#include "nsCOMPtr.h" +#include "nsCOMArray.h" +#include "nsPrintfCString.h" +#include "nsProxyRelease.h" +#include "nsServiceManagerUtils.h" +#include "nsITimer.h" +#include "nsThreadManager.h" +#include "nsThreadUtils.h" +#include "nsPIDOMWindow.h" +#include "nsIObserverService.h" +#include "nsIOService.h" +#include "nsIGlobalObject.h" +#include "nsIXPConnect.h" +#ifdef MOZ_GECKO_PROFILER +# include "GeckoProfilerReporter.h" +#endif +#if defined(XP_UNIX) || defined(MOZ_DMD) +# include "nsMemoryInfoDumper.h" +#endif +#include "nsNetCID.h" +#include "nsThread.h" +#include "VRProcessManager.h" +#include "mozilla/Attributes.h" +#include "mozilla/MemoryReportingProcess.h" +#include "mozilla/PodOperations.h" +#include "mozilla/Preferences.h" +#include "mozilla/RDDProcessManager.h" +#include "mozilla/ResultExtensions.h" +#include "mozilla/Services.h" +#include "mozilla/Telemetry.h" +#include "mozilla/UniquePtrExtensions.h" +#include "mozilla/dom/MemoryReportTypes.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/gfx/GPUProcessManager.h" +#include "mozilla/ipc/UtilityProcessManager.h" +#include "mozilla/ipc/FileDescriptorUtils.h" +#ifdef MOZ_PHC +# include "PHC.h" +#endif + +#ifdef MOZ_WIDGET_ANDROID +# include "mozilla/java/GeckoAppShellWrappers.h" +# include "mozilla/jni/Utils.h" +#endif + +#ifdef XP_WIN +# include "mozilla/MemoryInfo.h" + +# include <process.h> +# ifndef getpid +# define getpid _getpid +# endif +#else +# include <unistd.h> +#endif + +using namespace mozilla; +using namespace mozilla::ipc; +using namespace dom; + +#if defined(XP_LINUX) + +# include "mozilla/MemoryMapping.h" + +# include <malloc.h> +# include <string.h> +# include <stdlib.h> + +[[nodiscard]] static 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; +} + +[[nodiscard]] static nsresult GetProcSelfSmapsPrivate(int64_t* aN, pid_t aPid) { + // 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. + + nsTArray<MemoryMapping> mappings(1024); + MOZ_TRY(GetMemoryMappings(mappings, aPid)); + + int64_t amount = 0; + for (auto& mapping : mappings) { + amount += mapping.Private_Clean(); + amount += mapping.Private_Dirty(); + } + *aN = amount; + return NS_OK; +} + +# define HAVE_VSIZE_AND_RESIDENT_REPORTERS 1 +[[nodiscard]] static nsresult VsizeDistinguishedAmount(int64_t* aN) { + return GetProcSelfStatmField(0, aN); +} + +[[nodiscard]] static nsresult ResidentDistinguishedAmount(int64_t* aN) { + return GetProcSelfStatmField(1, aN); +} + +[[nodiscard]] static nsresult ResidentFastDistinguishedAmount(int64_t* aN) { + return ResidentDistinguishedAmount(aN); +} + +# define HAVE_RESIDENT_UNIQUE_REPORTER 1 +[[nodiscard]] static nsresult ResidentUniqueDistinguishedAmount( + int64_t* aN, pid_t aPid = 0) { + return GetProcSelfSmapsPrivate(aN, aPid); +} + +# ifdef HAVE_MALLINFO +# define HAVE_SYSTEM_HEAP_REPORTER 1 +[[nodiscard]] static 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 + +[[nodiscard]] static nsresult GetKinfoProcSelf(KINFO_PROC* aProc) { +# if defined(__OpenBSD__) && defined(MOZ_SANDBOX) + static LazyLogModule sPledgeLog("SandboxPledge"); + MOZ_LOG(sPledgeLog, LogLevel::Debug, + ("%s called when pledged, returning NS_ERROR_FAILURE\n", __func__)); + return NS_ERROR_FAILURE; +# endif + 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 +[[nodiscard]] static nsresult VsizeDistinguishedAmount(int64_t* aN) { + KINFO_PROC proc; + nsresult rv = GetKinfoProcSelf(&proc); + if (NS_SUCCEEDED(rv)) { + *aN = KP_SIZE(proc); + } + return rv; +} + +[[nodiscard]] static nsresult ResidentDistinguishedAmount(int64_t* aN) { + KINFO_PROC proc; + nsresult rv = GetKinfoProcSelf(&proc); + if (NS_SUCCEEDED(rv)) { + *aN = KP_RSS(proc); + } + return rv; +} + +[[nodiscard]] static nsresult ResidentFastDistinguishedAmount(int64_t* aN) { + return ResidentDistinguishedAmount(aN); +} + +# ifdef __FreeBSD__ +# include <libutil.h> +# include <algorithm> + +[[nodiscard]] static 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 +[[nodiscard]] static 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 +[[nodiscard]] static 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, + int64_t& aShared) { + aVsize = -1; + aResident = -1; + aShared = -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; + aShared = 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; + if (prmapp[i].pr_mflags & MA_SHARED) { + aShared += prmapp[i].pr_rss * prmapp[i].pr_pagesize; + } + } + break; + } + free(prmapp); + } + free(prmapp); + } + close(mapfd); + } +} + +# define HAVE_VSIZE_AND_RESIDENT_REPORTERS 1 +[[nodiscard]] static nsresult VsizeDistinguishedAmount(int64_t* aN) { + int64_t vsize, resident, shared; + XMappingIter(vsize, resident, shared); + if (vsize == -1) { + return NS_ERROR_FAILURE; + } + *aN = vsize; + return NS_OK; +} + +[[nodiscard]] static nsresult ResidentDistinguishedAmount(int64_t* aN) { + int64_t vsize, resident, shared; + XMappingIter(vsize, resident, shared); + if (resident == -1) { + return NS_ERROR_FAILURE; + } + *aN = resident; + return NS_OK; +} + +[[nodiscard]] static nsresult ResidentFastDistinguishedAmount(int64_t* aN) { + return ResidentDistinguishedAmount(aN); +} + +# define HAVE_RESIDENT_UNIQUE_REPORTER 1 +[[nodiscard]] static nsresult ResidentUniqueDistinguishedAmount(int64_t* aN) { + int64_t vsize, resident, shared; + XMappingIter(vsize, resident, shared); + if (resident == -1) { + return NS_ERROR_FAILURE; + } + *aN = resident - shared; + return NS_OK; +} + +#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> + +[[nodiscard]] static 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 +[[nodiscard]] static 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. +[[nodiscard]] static nsresult ResidentDistinguishedAmountHelper(int64_t* aN, + bool aDoPurge) { +# ifdef HAVE_JEMALLOC_STATS + if (aDoPurge) { + Telemetry::AutoTimer<Telemetry::MEMORY_FREE_PURGED_PAGES_MS> timer; + jemalloc_purge_freed_pages(); + } +# endif + + task_basic_info ti; + if (!GetTaskBasicInfo(&ti)) { + return NS_ERROR_FAILURE; + } + *aN = ti.resident_size; + return NS_OK; +} + +[[nodiscard]] static nsresult ResidentFastDistinguishedAmount(int64_t* aN) { + return ResidentDistinguishedAmountHelper(aN, /* doPurge = */ false); +} + +[[nodiscard]] static 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_ARM64: + base = SHARED_REGION_BASE_ARM64; + size = SHARED_REGION_SIZE_ARM64; + 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); +} + +[[nodiscard]] static nsresult ResidentUniqueDistinguishedAmount( + int64_t* aN, mach_port_t aPort = 0) { + 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 topSize = 0; + for (mach_vm_address_t addr = MACH_VM_MIN_ADDRESS;; addr += topSize) { + vm_region_top_info_data_t topInfo; + mach_msg_type_number_t topInfoCount = VM_REGION_TOP_INFO_COUNT; + mach_port_t topObjectName; + + kern_return_t kr = mach_vm_region( + aPort ? aPort : mach_task_self(), &addr, &topSize, VM_REGION_TOP_INFO, + reinterpret_cast<vm_region_info_t>(&topInfo), &topInfoCount, + &topObjectName); + if (kr == KERN_INVALID_ADDRESS) { + // Done iterating VM regions. + break; + } else if (kr != KERN_SUCCESS) { + return NS_ERROR_FAILURE; + } + + if (InSharedRegion(addr, cpu_type) && topInfo.share_mode != SM_PRIVATE) { + continue; + } + + switch (topInfo.share_mode) { + case SM_LARGE_PAGE: + // NB: Large pages are not shareable and always resident. + case SM_PRIVATE: + privatePages += topInfo.private_pages_resident; + privatePages += topInfo.shared_pages_resident; + break; + case SM_COW: + privatePages += topInfo.private_pages_resident; + if (topInfo.ref_count == 1) { + // Treat copy-on-write pages as private if they only have one + // reference. + privatePages += topInfo.shared_pages_resident; + } + break; + case SM_SHARED: { + // Using mprotect() or similar to protect a page in the middle of a + // mapping can create aliased mappings. They look like shared mappings + // to the VM_REGION_TOP_INFO interface, so re-check with + // VM_REGION_EXTENDED_INFO. + + mach_vm_size_t exSize = 0; + vm_region_extended_info_data_t exInfo; + mach_msg_type_number_t exInfoCount = VM_REGION_EXTENDED_INFO_COUNT; + mach_port_t exObjectName; + kr = mach_vm_region(aPort ? aPort : mach_task_self(), &addr, &exSize, + VM_REGION_EXTENDED_INFO, + reinterpret_cast<vm_region_info_t>(&exInfo), + &exInfoCount, &exObjectName); + if (kr == KERN_INVALID_ADDRESS) { + // Done iterating VM regions. + break; + } else if (kr != KERN_SUCCESS) { + return NS_ERROR_FAILURE; + } + + if (exInfo.share_mode == SM_PRIVATE_ALIASED) { + privatePages += exInfo.pages_resident; + } + break; + } + default: + break; + } + } + + vm_size_t pageSize; + if (host_page_size(aPort ? aPort : mach_task_self(), &pageSize) != + KERN_SUCCESS) { + pageSize = PAGE_SIZE; + } + + *aN = privatePages * pageSize; + return NS_OK; +} + +[[nodiscard]] static nsresult PhysicalFootprintAmount(int64_t* aN, + mach_port_t aPort = 0) { + MOZ_ASSERT(aN); + + // The phys_footprint value (introduced in 10.11) of the TASK_VM_INFO data + // matches the value in the 'Memory' column of the Activity Monitor. + task_vm_info_data_t task_vm_info; + mach_msg_type_number_t count = TASK_VM_INFO_COUNT; + kern_return_t kr = task_info(aPort ? aPort : mach_task_self(), TASK_VM_INFO, + (task_info_t)&task_vm_info, &count); + if (kr != KERN_SUCCESS) { + return NS_ERROR_FAILURE; + } + + *aN = task_vm_info.phys_footprint; + return NS_OK; +} + +#elif defined(XP_WIN) + +# include <windows.h> +# include <psapi.h> +# include <algorithm> + +# define HAVE_VSIZE_AND_RESIDENT_REPORTERS 1 +[[nodiscard]] static 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; +} + +[[nodiscard]] static 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; +} + +[[nodiscard]] static nsresult ResidentFastDistinguishedAmount(int64_t* aN) { + return ResidentDistinguishedAmount(aN); +} + +# define HAVE_RESIDENT_UNIQUE_REPORTER 1 + +[[nodiscard]] static nsresult ResidentUniqueDistinguishedAmount( + int64_t* aN, HANDLE aProcess = nullptr) { + // Determine how many entries we need. + PSAPI_WORKING_SET_INFORMATION tmp; + DWORD tmpSize = sizeof(tmp); + memset(&tmp, 0, tmpSize); + + HANDLE proc = aProcess ? aProcess : 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 +[[nodiscard]] static 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 +[[nodiscard]] static 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, but we should not touch non-default +// heaps because they may be destroyed at anytime while we hold a handle. So we +// count only the default heap. +[[nodiscard]] static nsresult SystemHeapSize(int64_t* aSizeOut) { + HANDLE heap = GetProcessHeap(); + + 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); + + *aSizeOut = heapSize; + 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(""_ns, path, KIND_OTHER, UNITS_BYTES, + entry->mSize, "From MEMORY_BASIC_INFORMATION."_ns, + 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))) { + // clang-format off + 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."); + // clang-format on + } + return NS_OK; + } +}; +NS_IMPL_ISUPPORTS(PrivateReporter, nsIMemoryReporter) +#endif + +#ifdef HAVE_VSIZE_AND_RESIDENT_REPORTERS +class VsizeReporter final : public nsIMemoryReporter { + ~VsizeReporter() = default; + + public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override { + int64_t amount; + if (NS_SUCCEEDED(VsizeDistinguishedAmount(&amount))) { + // clang-format off + 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."); + // clang-format on + } + return NS_OK; + } +}; +NS_IMPL_ISUPPORTS(VsizeReporter, nsIMemoryReporter) + +class ResidentReporter final : public nsIMemoryReporter { + ~ResidentReporter() = default; + + public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override { + int64_t amount; + if (NS_SUCCEEDED(ResidentDistinguishedAmount(&amount))) { + // clang-format off + 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."); + // clang-format on + } + 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() = default; + + public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override { + int64_t amount = 0; + // clang-format off + 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."); + } +#ifdef XP_MACOSX + if (NS_SUCCEEDED(PhysicalFootprintAmount(&amount))) { + MOZ_COLLECT_REPORT( + "resident-phys-footprint", KIND_OTHER, UNITS_BYTES, amount, +"Memory footprint reported by MacOS's task_info API's phys_footprint field. " +"This matches the memory column in Activity Monitor."); + } +#endif + // clang-format on + return NS_OK; + } +}; +NS_IMPL_ISUPPORTS(ResidentUniqueReporter, nsIMemoryReporter) + +#endif // HAVE_RESIDENT_UNIQUE_REPORTER + +#ifdef HAVE_SYSTEM_HEAP_REPORTER + +class SystemHeapReporter final : public nsIMemoryReporter { + ~SystemHeapReporter() = default; + + public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override { + int64_t amount; + if (NS_SUCCEEDED(SystemHeapSize(&amount))) { + // clang-format off + 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."); + // clang-format on + } + 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 + +[[nodiscard]] static 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() = default; + + 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() = default; + + 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; + // clang-format off + 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."); + // clang-format on + } + return NS_OK; + } +}; +NS_IMPL_ISUPPORTS(PageFaultsSoftReporter, nsIMemoryReporter) + +[[nodiscard]] static 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() = default; + + public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override { + int64_t amount = 0; + if (NS_SUCCEEDED(PageFaultsHardDistinguishedAmount(&amount))) { + // clang-format off + 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."); + // clang-format on + } + 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(const 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 nsMemoryReporterManager::HeapOverheadFraction( + const 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() = default; + + public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override { + jemalloc_stats_t stats; + const size_t num_bins = jemalloc_stats_num_bins(); + nsTArray<jemalloc_bin_stats_t> bin_stats(num_bins); + bin_stats.SetLength(num_bins); + jemalloc_stats(&stats, bin_stats.Elements()); + + // clang-format off + 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. + for (auto& bin : bin_stats) { + MOZ_ASSERT(bin.size); + nsPrintfCString path("explicit/heap-overhead/bin-unused/bin-%zu", + bin.size); + aHandleReport->Callback(EmptyCString(), path, KIND_NONHEAP, UNITS_BYTES, + bin.bytes_unused, + nsLiteralCString( + "Unused bytes in all runs of all bins for this size class"), + aData); + } + + 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."); + +#ifdef MOZ_PHC + mozilla::phc::MemoryUsage usage; + mozilla::phc::PHCMemoryUsage(usage); + + MOZ_COLLECT_REPORT( + "explicit/heap-overhead/phc/metadata", KIND_NONHEAP, UNITS_BYTES, + usage.mMetadataBytes, +"Memory used by PHC to store stacks and other metadata for each allocation"); + MOZ_COLLECT_REPORT( + "explicit/heap-overhead/phc/fragmentation", KIND_NONHEAP, UNITS_BYTES, + usage.mFragmentationBytes, +"The amount of memory lost due to rounding up allocations to the next page " +"size. " +"This is also known as 'internal fragmentation'. " +"Note that all allocators have some internal fragmentation, there may still " +"be some internal fragmentation without PHC."); +#endif + + // clang-format on + + 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() = default; + + public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override { + AtomsSizes sizes; + NS_AddSizeOfAtoms(MallocSizeOf, sizes); + + MOZ_COLLECT_REPORT("explicit/atoms/table", KIND_HEAP, UNITS_BYTES, + sizes.mTable, "Memory used by the atom table."); + + MOZ_COLLECT_REPORT( + "explicit/atoms/dynamic-objects-and-chars", KIND_HEAP, UNITS_BYTES, + sizes.mDynamicAtoms, + "Memory used by dynamic atom objects and chars (which are stored " + "at the end of each atom object)."); + + return NS_OK; + } +}; +NS_IMPL_ISUPPORTS(AtomTablesReporter, nsIMemoryReporter) + +class ThreadsReporter final : public nsIMemoryReporter { + MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf) + ~ThreadsReporter() = default; + + public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override { +#ifdef XP_LINUX + nsTArray<MemoryMapping> mappings(1024); + MOZ_TRY(GetMemoryMappings(mappings)); +#endif + + // Enumerating over active threads requires holding a lock, so we collect + // info on all threads, and then call our reporter callbacks after releasing + // the lock. + struct ThreadData { + nsCString mName; + uint32_t mThreadId; + size_t mPrivateSize; + }; + AutoTArray<ThreadData, 32> threads; + + size_t eventQueueSizes = 0; + size_t wrapperSizes = 0; + size_t threadCount = 0; + + { + nsThreadManager& tm = nsThreadManager::get(); + OffTheBooksMutexAutoLock lock(tm.ThreadListMutex()); + for (auto* thread : tm.ThreadList()) { + threadCount++; + eventQueueSizes += thread->SizeOfEventQueues(MallocSizeOf); + wrapperSizes += thread->ShallowSizeOfIncludingThis(MallocSizeOf); + + if (!thread->StackBase()) { + continue; + } + +#if defined(XP_LINUX) + int idx = mappings.BinaryIndexOf(thread->StackBase()); + if (idx < 0) { + continue; + } + // Referenced() is the combined size of all pages in the region which + // have ever been touched, and are therefore consuming memory. For stack + // regions, these pages are guaranteed to be un-shared unless we fork + // after creating threads (which we don't). + size_t privateSize = mappings[idx].Referenced(); + + // On Linux, we have to be very careful matching memory regions to + // thread stacks. + // + // To begin with, the kernel only reports VM stats for regions of all + // adjacent pages with the same flags, protection, and backing file. + // There's no way to get finer-grained usage information for a subset of + // those pages. + // + // Stack segments always have a guard page at the bottom of the stack + // (assuming we only support stacks that grow down), so there's no + // danger of them being merged with other stack regions. At the top, + // there's no protection page, and no way to allocate one without using + // pthreads directly and allocating our own stacks. So we get around the + // problem by adding an extra VM flag (NOHUGEPAGES) to our stack region, + // which we don't expect to be set on any heap regions. But this is not + // fool-proof. + // + // A second kink is that different C libraries (and different versions + // thereof) report stack base locations and sizes differently with + // regard to the guard page. For the libraries that include the guard + // page in the stack size base pointer, we need to adjust those values + // to compensate. But it's possible that our logic will get out of sync + // with library changes, or someone will compile with an unexpected + // library. + // + // + // The upshot of all of this is that there may be configurations that + // our special cases don't cover. And if there are, we want to know + // about it. So assert that total size of the memory region we're + // reporting actually matches the allocated size of the thread stack. +# ifndef ANDROID + MOZ_ASSERT(mappings[idx].Size() == thread->StackSize(), + "Mapping region size doesn't match stack allocation size"); +# endif +#elif defined(XP_WIN) + auto memInfo = + MemoryInfo::Get(thread->StackBase(), thread->StackSize()); + size_t privateSize = memInfo.Committed(); +#else + size_t privateSize = thread->StackSize(); + MOZ_ASSERT_UNREACHABLE( + "Shouldn't have stack base pointer on this " + "platform"); +#endif + + nsCString threadName; + thread->GetThreadName(threadName); + threads.AppendElement(ThreadData{ + std::move(threadName), + thread->ThreadId(), + // On Linux, it's possible (but unlikely) that our stack region will + // have been merged with adjacent heap regions, in which case we'll + // get combined size information for both. So we take the minimum of + // the reported private size and the requested stack size to avoid + // the possible of majorly over-reporting in that case. + std::min(privateSize, thread->StackSize()), + }); + } + } + + for (auto& thread : threads) { + nsPrintfCString path("explicit/threads/stacks/%s (tid=%u)", + thread.mName.get(), thread.mThreadId); + + aHandleReport->Callback( + ""_ns, path, KIND_NONHEAP, UNITS_BYTES, thread.mPrivateSize, + nsLiteralCString("The sizes of thread stacks which have been " + "committed to memory."), + aData); + } + + MOZ_COLLECT_REPORT("explicit/threads/overhead/event-queues", KIND_HEAP, + UNITS_BYTES, eventQueueSizes, + "The sizes of nsThread event queues and observers."); + + MOZ_COLLECT_REPORT("explicit/threads/overhead/wrappers", KIND_HEAP, + UNITS_BYTES, wrapperSizes, + "The sizes of nsThread/PRThread wrappers."); + +#if defined(XP_WIN) + // Each thread on Windows has a fixed kernel overhead. For 32 bit Windows, + // that's 12K. For 64 bit, it's 24K. + // + // See + // https://blogs.technet.microsoft.com/markrussinovich/2009/07/05/pushing-the-limits-of-windows-processes-and-threads/ + constexpr size_t kKernelSize = (sizeof(void*) == 8 ? 24 : 12) * 1024; +#elif defined(XP_LINUX) + // On Linux, kernel stacks are usually 8K. However, on x86, they are + // allocated virtually, and start out at 4K. They may grow to 8K, but we + // have no way of knowing which ones do, so all we can do is guess. +# if defined(__x86_64__) || defined(__i386__) + constexpr size_t kKernelSize = 4 * 1024; +# else + constexpr size_t kKernelSize = 8 * 1024; +# endif +#elif defined(XP_MACOSX) + // On Darwin, kernel stacks are 16K: + // + // https://books.google.com/books?id=K8vUkpOXhN4C&lpg=PA513&dq=mach%20kernel%20thread%20stack%20size&pg=PA513#v=onepage&q=mach%20kernel%20thread%20stack%20size&f=false + constexpr size_t kKernelSize = 16 * 1024; +#else + // Elsewhere, just assume that kernel stacks require at least 8K. + constexpr size_t kKernelSize = 8 * 1024; +#endif + + MOZ_COLLECT_REPORT("explicit/threads/overhead/kernel", KIND_NONHEAP, + UNITS_BYTES, threadCount * kKernelSize, + "The total kernel overhead for all active threads."); + + return NS_OK; + } +}; +NS_IMPL_ISUPPORTS(ThreadsReporter, 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() = default; + + 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() = default; +}; +NS_IMPL_ISUPPORTS(DMDReporter, nsIMemoryReporter) + +} // namespace dmd +} // namespace mozilla + +#endif // MOZ_DMD + +#ifdef MOZ_WIDGET_ANDROID +class AndroidMemoryReporter final : public nsIMemoryReporter { + public: + NS_DECL_ISUPPORTS + + AndroidMemoryReporter() = default; + + NS_IMETHOD + CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData, + bool aAnonymize) override { + if (!jni::IsAvailable() || jni::GetAPIVersion() < 23) { + return NS_OK; + } + + int32_t heap = java::GeckoAppShell::GetMemoryUsage("summary.java-heap"_ns); + if (heap > 0) { + MOZ_COLLECT_REPORT("java-heap", KIND_OTHER, UNITS_BYTES, heap * 1024, + "The private Java Heap usage"); + } + return NS_OK; + } + + private: + ~AndroidMemoryReporter() = default; +}; + +NS_IMPL_ISUPPORTS(AndroidMemoryReporter, nsIMemoryReporter) +#endif + +/** + ** nsMemoryReporterManager implementation + **/ + +NS_IMPL_ISUPPORTS(nsMemoryReporterManager, nsIMemoryReporterManager, + nsIMemoryReporter) + +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; + +#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()); + + RegisterStrongReporter(new ThreadsReporter()); + +#ifdef DEBUG + RegisterStrongReporter(new DeadlockDetectorReporter()); +#endif + +#ifdef MOZ_GECKO_PROFILER + // We have to register this here rather than in profiler_init() because + // profiler_init() runs prior to nsMemoryReporterManager's creation. + RegisterStrongReporter(new GeckoProfilerReporter()); +#endif + +#ifdef MOZ_DMD + RegisterStrongReporter(new mozilla::dmd::DMDReporter()); +#endif + +#ifdef XP_WIN + RegisterStrongReporter(new WindowsAddressSpaceReporter()); +#endif + +#ifdef MOZ_WIDGET_ANDROID + RegisterStrongReporter(new AndroidMemoryReporter()); +#endif + +#ifdef XP_UNIX + nsMemoryInfoDumper::Initialize(); +#endif + + // Report our own memory usage as well. + RegisterWeakReporter(this); + + 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) +#ifdef HAVE_JEMALLOC_STATS + , + mThreadPool(do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID)) +#endif +{ +} + +nsMemoryReporterManager::~nsMemoryReporterManager() { + delete mStrongReporters; + delete mWeakReporters; + NS_ASSERTION(!mSavedStrongReporters, "failed to restore strong reporters"); + NS_ASSERTION(!mSavedWeakReporters, "failed to restore weak reporters"); +} + +NS_IMETHODIMP +nsMemoryReporterManager::CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) { + size_t n = MallocSizeOf(this); + { + mozilla::MutexAutoLock autoLock(mMutex); + n += mStrongReporters->ShallowSizeOfIncludingThis(MallocSizeOf); + n += mWeakReporters->ShallowSizeOfIncludingThis(MallocSizeOf); + } + + MOZ_COLLECT_REPORT("explicit/memory-reporter-manager", KIND_HEAP, UNITS_BYTES, + n, "Memory used by the memory reporter infrastructure."); + + return NS_OK; +} + +#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 = */ u""_ns); +} + +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("nsMemoryReporterManager::StartGettingReports", this, + &nsMemoryReporterManager::StartGettingReports); + rv = MinimizeMemoryUsage(callback); + } else { + rv = StartGettingReports(); + } + return rv; +} + +// MainThread only +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<dom::ContentParent*> childWeakRefs; + dom::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]); + } + } + + if (gfx::GPUProcessManager* gpu = gfx::GPUProcessManager::Get()) { + if (RefPtr<MemoryReportingProcess> proc = gpu->GetProcessMemoryReporter()) { + s->mChildrenPending.AppendElement(proc.forget()); + } + } + + if (RDDProcessManager* rdd = RDDProcessManager::Get()) { + if (RefPtr<MemoryReportingProcess> proc = rdd->GetProcessMemoryReporter()) { + s->mChildrenPending.AppendElement(proc.forget()); + } + } + + if (gfx::VRProcessManager* vr = gfx::VRProcessManager::Get()) { + if (RefPtr<MemoryReportingProcess> proc = vr->GetProcessMemoryReporter()) { + s->mChildrenPending.AppendElement(proc.forget()); + } + } + + if (!IsRegistrationBlocked() && net::gIOService) { + if (RefPtr<MemoryReportingProcess> proc = + net::gIOService->GetSocketProcessMemoryReporter()) { + s->mChildrenPending.AppendElement(proc.forget()); + } + } + + if (!IsRegistrationBlocked()) { + if (RefPtr<UtilityProcessManager> utility = + UtilityProcessManager::GetIfExists()) { + for (RefPtr<UtilityProcessParent>& parent : + utility->GetAllProcessesProcessParent()) { + if (RefPtr<MemoryReportingProcess> proc = + utility->GetProcessMemoryReporter(parent)) { + s->mChildrenPending.AppendElement(proc.forget()); + } + } + } + } + + if (!s->mChildrenPending.IsEmpty()) { + nsCOMPtr<nsITimer> timer; + rv = NS_NewTimerWithFuncCallback( + getter_AddRefs(timer), TimeoutCallback, this, kTimeoutLengthMS, + nsITimer::TYPE_ONE_SHOT, + "nsMemoryReporterManager::StartGettingReports"); + 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( + "nsMemoryReporterManager::DispatchReporter", + [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 (const auto& entry : *mStrongReporters) { + DispatchReporter(entry.GetKey(), entry.GetData(), aHandleReport, + aHandleReportData, aAnonymize); + } + + for (const auto& entry : *mWeakReporters) { + nsCOMPtr<nsIMemoryReporter> reporter = entry.GetKey(); + DispatchReporter(reporter, entry.GetData(), aHandleReport, + aHandleReportData, aAnonymize); + } + } + + return NS_OK; +} + +// MainThread only +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::MemoryReportingProcess* 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; + } + + Maybe<mozilla::ipc::FileDescriptor> dmdFileDesc; +#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 = Some(mozilla::ipc::FILEToFileDescriptor(dmdFile)); + fclose(dmdFile); + } + } +#endif + return aChild->SendRequestMemoryReport( + 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 + const RefPtr<MemoryReportingProcess> nextChild = + s->mChildrenPending.PopLastElement(); + // 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), + 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->InsertOrUpdate(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->InsertOrUpdate(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 +} + +#ifdef XP_MACOSX +/*static*/ +int64_t nsMemoryReporterManager::PhysicalFootprint(mach_port_t aPort) { + int64_t amount = 0; + nsresult rv = PhysicalFootprintAmount(&amount, aPort); + NS_ENSURE_SUCCESS(rv, 0); + return amount; +} +#endif + +typedef +#ifdef XP_WIN + HANDLE +#elif XP_MACOSX + mach_port_t +#elif XP_LINUX + pid_t +#else + int /*dummy type */ +#endif + ResidentUniqueArg; + +#if defined(XP_WIN) || defined(XP_MACOSX) || defined(XP_LINUX) + +/*static*/ +int64_t nsMemoryReporterManager::ResidentUnique(ResidentUniqueArg aProcess) { + int64_t amount = 0; + nsresult rv = ResidentUniqueDistinguishedAmount(&amount, aProcess); + NS_ENSURE_SUCCESS(rv, 0); + return amount; +} + +#else + +/*static*/ +int64_t nsMemoryReporterManager::ResidentUnique(ResidentUniqueArg) { +# ifdef HAVE_RESIDENT_UNIQUE_REPORTER + int64_t amount = 0; + nsresult rv = ResidentUniqueDistinguishedAmount(&amount); + NS_ENSURE_SUCCESS(rv, 0); + return amount; +# else + return 0; +# endif +} + +#endif // XP_{WIN, MACOSX, LINUX, *} + +#ifdef HAVE_JEMALLOC_STATS +// static +size_t nsMemoryReporterManager::HeapAllocated(const jemalloc_stats_t& aStats) { + return aStats.allocated; +} +#endif + +NS_IMETHODIMP +nsMemoryReporterManager::GetHeapAllocated(int64_t* aAmount) { +#ifdef HAVE_JEMALLOC_STATS + jemalloc_stats_t stats; + jemalloc_stats(&stats); + *aAmount = HeapAllocated(stats); + 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 +} + +[[nodiscard]] static 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::GetJSMainRuntimeRealmsSystem(int64_t* aAmount) { + return GetInfallibleAmount(mAmountFns.mJSMainRuntimeRealmsSystem, aAmount); +} + +NS_IMETHODIMP +nsMemoryReporterManager::GetJSMainRuntimeRealmsUser(int64_t* aAmount) { + return GetInfallibleAmount(mAmountFns.mJSMainRuntimeRealmsUser, 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::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) + : mozilla::Runnable("MinimizeMemoryUsageRunnable"), + 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, JSMainRuntimeRealmsSystem) +DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, JSMainRuntimeRealmsUser) + +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, 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 0000000000..7064524521 --- /dev/null +++ b/xpcom/base/nsMemoryReporterManager.h @@ -0,0 +1,321 @@ +/* -*- 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 "nsTHashMap.h" +#include "nsHashKeys.h" +#include "nsIMemoryReporter.h" +#include "nsISupports.h" +#include "nsServiceManagerUtils.h" + +#ifdef XP_WIN +# include <windows.h> +#endif // XP_WIN + +#if defined(MOZ_MEMORY) +# define HAVE_JEMALLOC_STATS 1 +# include "mozmemory.h" +#endif // MOZ_MEMORY + +namespace mozilla { +class MemoryReportingProcess; +namespace dom { +class MemoryReport; +} // namespace dom +} // namespace mozilla + +class mozIDOMWindowProxy; +class nsIEventTarget; +class nsIRunnable; +class nsITimer; + +class nsMemoryReporterManager final : public nsIMemoryReporterManager, + public nsIMemoryReporter { + virtual ~nsMemoryReporterManager(); + + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIMEMORYREPORTERMANAGER + NS_DECL_NSIMEMORYREPORTER + + MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf) + + nsMemoryReporterManager(); + + // Gets the memory reporter manager service. + static already_AddRefed<nsMemoryReporterManager> GetOrCreate() { + nsCOMPtr<nsIMemoryReporterManager> imgr = + do_GetService("@mozilla.org/memory-reporter-manager;1"); + return imgr.forget().downcast<nsMemoryReporterManager>(); + } + + typedef nsTHashMap<nsRefPtrHashKey<nsIMemoryReporter>, bool> + StrongReportersTable; + typedef nsTHashMap<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 = nullptr; + mozilla::InfallibleAmountFn mJSMainRuntimeTemporaryPeak = nullptr; + mozilla::InfallibleAmountFn mJSMainRuntimeCompartmentsSystem = nullptr; + mozilla::InfallibleAmountFn mJSMainRuntimeCompartmentsUser = nullptr; + mozilla::InfallibleAmountFn mJSMainRuntimeRealmsSystem = nullptr; + mozilla::InfallibleAmountFn mJSMainRuntimeRealmsUser = nullptr; + + mozilla::InfallibleAmountFn mImagesContentUsedUncompressed = nullptr; + + mozilla::InfallibleAmountFn mStorageSQLite = nullptr; + + mozilla::InfallibleAmountFn mLowMemoryEventsPhysical = nullptr; + + mozilla::InfallibleAmountFn mGhostWindows = nullptr; + }; + 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. + // + // Returns 0 if, for some reason, the resident unique memory cannot be + // determined - typically if there is a race between us and someone else + // closing the process and we lost that race. +#ifdef XP_WIN + static int64_t ResidentUnique(HANDLE aProcess = nullptr); +#elif XP_MACOSX + // On MacOS this can sometimes be significantly slow. It should not be used + // except in debugging or at the request of a user (eg about:memory). + static int64_t ResidentUnique(mach_port_t aPort = 0); +#else + static int64_t ResidentUnique(pid_t aPid = 0); +#endif // XP_{WIN, MACOSX, LINUX, *} + +#ifdef XP_MACOSX + // Retrive the "phys_footprint" memory statistic on MacOS. + static int64_t PhysicalFootprint(mach_port_t aPort = 0); +#endif + + // Functions that measure per-tab memory consumption. + struct SizeOfTabFns { + mozilla::JSSizeOfTabFn mJS = nullptr; + mozilla::NonJSSizeOfTabFn mNonJS = nullptr; + }; + SizeOfTabFns mSizeOfTabFns; + +#ifdef HAVE_JEMALLOC_STATS + // These C++ only versions of HeapAllocated and HeapOverheadFraction avoid + // extra calls to jemalloc_stats; + static size_t HeapAllocated(const jemalloc_stats_t& stats); + static int64_t HeapOverheadFraction(const jemalloc_stats_t& stats); +#endif + + private: + bool IsRegistrationBlocked() MOZ_EXCLUDES(mMutex) { + mozilla::MutexAutoLock lock(mMutex); + return mIsRegistrationBlocked; + } + + [[nodiscard]] nsresult RegisterReporterHelper(nsIMemoryReporter* aReporter, + bool aForce, bool aStrongRef, + bool aIsAsync); + + [[nodiscard]] nsresult StartGettingReports(); + // No [[nodiscard]] 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 = 180000; + + mozilla::Mutex mMutex; + bool mIsRegistrationBlocked MOZ_GUARDED_BY(mMutex); + + StrongReportersTable* mStrongReporters MOZ_GUARDED_BY(mMutex); + WeakReportersTable* mWeakReporters MOZ_GUARDED_BY(mMutex); + + // These two are only used for testing purposes. + StrongReportersTable* mSavedStrongReporters MOZ_GUARDED_BY(mMutex); + WeakReportersTable* mSavedWeakReporters MOZ_GUARDED_BY(mMutex); + + uint32_t mNextGeneration; // MainThread only + + // 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::MemoryReportingProcess>> 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; // MainThread only + + // This is reinitialized each time a call to GetReports is initiated. + PendingReportersState* mPendingReportersState; // MainThread only + + // Used in GetHeapAllocatedAsync() to run jemalloc_stats async. + nsCOMPtr<nsIEventTarget> mThreadPool MOZ_GUARDED_BY(mMutex); + + PendingProcessesState* GetStateForGeneration(uint32_t aGeneration); + [[nodiscard]] static bool StartChildReport( + mozilla::MemoryReportingProcess* 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/nsObjCExceptions.h b/xpcom/base/nsObjCExceptions.h new file mode 100644 index 0000000000..e8c2059d8e --- /dev/null +++ b/xpcom/base/nsObjCExceptions.h @@ -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/. */ + +#ifndef nsObjCExceptions_h_ +#define nsObjCExceptions_h_ + +// Undo the damage that exception_defines.h does. +#undef try +#undef catch + +@class NSException; + +// See Mozilla bug 163260. +// This file can only be included in an Objective-C context. + +void nsObjCExceptionLog(NSException* aException); + +namespace mozilla { + +// Check if this is an exception that's outside of our control. +// Called when an exception bubbles up to the native event loop. +bool ShouldIgnoreObjCException(NSException* aException); + +} // namespace mozilla + +// For wrapping blocks of Obj-C calls which are not expected to throw exception. +// Causes a MOZ_CRASH if an Obj-C exception is encountered. +#define NS_OBJC_BEGIN_TRY_ABORT_BLOCK @try { +#define NS_OBJC_END_TRY_ABORT_BLOCK \ + } \ + @catch (NSException * _exn) { \ + nsObjCExceptionLog(_exn); \ + MOZ_CRASH("Encountered unexpected Objective C exception"); \ + } + +// For wrapping blocks of Obj-C calls. Logs the exception and moves on. +#define NS_OBJC_BEGIN_TRY_IGNORE_BLOCK @try { +#define NS_OBJC_END_TRY_IGNORE_BLOCK \ + } \ + @catch (NSException * _exn) { \ + nsObjCExceptionLog(_exn); \ + } + +// Same as above IGNORE_BLOCK but returns a value after the try/catch block. +#define NS_OBJC_BEGIN_TRY_BLOCK_RETURN @try { +#define NS_OBJC_END_TRY_BLOCK_RETURN(_rv) \ + } \ + @catch (NSException * _exn) { \ + nsObjCExceptionLog(_exn); \ + } \ + return _rv; + +#endif // nsObjCExceptions_h_ diff --git a/xpcom/base/nsObjCExceptions.mm b/xpcom/base/nsObjCExceptions.mm new file mode 100644 index 0000000000..77a7edc998 --- /dev/null +++ b/xpcom/base/nsObjCExceptions.mm @@ -0,0 +1,51 @@ +/* -*- 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 "nsObjCExceptions.h" + +#import <Foundation/Foundation.h> + +#include <unistd.h> +#include <signal.h> + +#include "nsICrashReporter.h" +#include "nsCOMPtr.h" +#include "nsServiceManagerUtils.h" + +#include "nsError.h" + +void nsObjCExceptionLog(NSException* aException) { + NSLog(@"Mozilla has caught an Obj-C exception [%@: %@]", [aException name], + [aException reason]); + + // 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)); + } + +#ifdef DEBUG + NSLog(@"Stack trace:\n%@", [aException callStackSymbols]); +#endif +} + +namespace mozilla { + +bool ShouldIgnoreObjCException(NSException* aException) { + // Ignore known exceptions that we've seen in crash reports, which shouldn't + // cause a MOZ_CRASH in Nightly. + if (aException.name == NSInternalInconsistencyException) { + if ([aException.reason containsString:@"Missing Touches."]) { + // Seen in bug 1694000. + return true; + } + } + return false; +} + +} // namespace mozilla diff --git a/xpcom/base/nsQueryObject.h b/xpcom/base/nsQueryObject.h new file mode 100644 index 0000000000..7cfbe954ce --- /dev/null +++ b/xpcom/base/nsQueryObject.h @@ -0,0 +1,93 @@ +/* -*- 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 override { + 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 override { + 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(const nsCOMPtr<T>& aRawPtr) { + return nsQueryObject<T>(aRawPtr); +} + +template <class T> +inline nsQueryObject<T> do_QueryObject(const 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(const nsCOMPtr<T>& aRawPtr, + nsresult* aErrorPtr) { + return nsQueryObjectWithError<T>(aRawPtr, aErrorPtr); +} + +template <class T> +inline nsQueryObjectWithError<T> do_QueryObject(const 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 0000000000..84074b57bc --- /dev/null +++ b/xpcom/base/nsSecurityConsoleMessage.cpp @@ -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/. */ + +#include "nsSecurityConsoleMessage.h" + +NS_IMPL_ISUPPORTS(nsSecurityConsoleMessage, nsISecurityConsoleMessage) + +nsSecurityConsoleMessage::nsSecurityConsoleMessage() = default; + +nsSecurityConsoleMessage::~nsSecurityConsoleMessage() = default; + +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 0000000000..9a74d3e38b --- /dev/null +++ b/xpcom/base/nsSecurityConsoleMessage.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 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/nsSystemInfo.cpp b/xpcom/base/nsSystemInfo.cpp new file mode 100644 index 0000000000..2b567bda98 --- /dev/null +++ b/xpcom/base/nsSystemInfo.cpp @@ -0,0 +1,1683 @@ +/* -*- 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 "nsAppRunner.h" +#include "nsSystemInfo.h" +#include "prsystem.h" +#include "prio.h" +#include "mozilla/SSE.h" +#include "mozilla/arm.h" +#include "mozilla/LazyIdleThread.h" +#include "mozilla/LookAndFeel.h" +#include "mozilla/Sprintf.h" +#include "mozilla/Try.h" +#include "jsapi.h" +#include "js/PropertyAndElement.h" // JS_SetProperty +#include "mozilla/dom/Promise.h" + +#ifdef XP_WIN +# include <comutil.h> +# include <time.h> +# ifndef __MINGW32__ +# include <iwscapi.h> +# endif // __MINGW32__ +# include <windows.h> +# include <winioctl.h> +# ifndef __MINGW32__ +# include <wrl.h> +# include <wscapi.h> +# endif // __MINGW32__ +# include "base/scoped_handle_win.h" +# include "mozilla/DynamicallyLinkedFunctionPtr.h" +# include "mozilla/WindowsVersion.h" +# include "nsAppDirectoryServiceDefs.h" +# include "nsDirectoryServiceDefs.h" +# include "nsDirectoryServiceUtils.h" +# include "nsWindowsHelpers.h" +# include "WinUtils.h" +# include "mozilla/NotNull.h" + +#endif + +#ifdef XP_MACOSX +# include "MacHelpers.h" +#endif + +#ifdef MOZ_WIDGET_GTK +# include <gtk/gtk.h> +# include <dlfcn.h> +# include "mozilla/WidgetUtilsGtk.h" +#endif + +#if defined(XP_LINUX) && !defined(ANDROID) +# include <unistd.h> +# include <fstream> +# include "mozilla/Tokenizer.h" +# include "mozilla/widget/LSBUtils.h" +# include "nsCharSeparatedTokenizer.h" + +# include <map> +# include <string> +#endif + +#ifdef MOZ_WIDGET_ANDROID +# include "AndroidBuild.h" +# include "mozilla/java/GeckoAppShellWrappers.h" +# include "mozilla/jni/Utils.h" +#endif + +#ifdef XP_MACOSX +# include <sys/sysctl.h> +#endif + +#if defined(XP_LINUX) && defined(MOZ_SANDBOX) +# include "mozilla/SandboxInfo.h" +#endif + +// Slot for NS_InitXPCOM 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; + +using namespace mozilla::dom; + +#if defined(XP_WIN) +# define RuntimeClass_Windows_System_Profile_WindowsIntegrityPolicy \ + L"Windows.System.Profile.WindowsIntegrityPolicy" +# ifndef __MINGW32__ +using namespace Microsoft::WRL; +using namespace Microsoft::WRL::Wrappers; +using namespace ABI::Windows::Foundation; +# endif // __MINGW32__ +#endif + +#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 + +#ifdef XP_WIN +// Lifted from media/webrtc/trunk/webrtc/base/systeminfo.cc, +// so keeping the _ instead of switching to camel case for now. +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 + + // 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 (!::GetLogicalProcessorInformation(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 + +#if defined(XP_WIN) +namespace { +static nsresult GetFolderDiskInfo(nsIFile* file, FolderDiskInfo& info) { + info.model.Truncate(); + info.revision.Truncate(); + info.isSSD = false; + + nsAutoString filePath; + nsresult rv = file->GetPath(filePath); + NS_ENSURE_SUCCESS(rv, rv); + wchar_t volumeMountPoint[MAX_PATH] = {L'\\', L'\\', L'.', L'\\'}; + const size_t PREFIX_LEN = 4; + if (!::GetVolumePathNameW( + filePath.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; + } + + queryParameters.PropertyId = StorageDeviceTrimProperty; + bytesRead = 0; + bool isSSD = false; + DEVICE_TRIM_DESCRIPTOR trimDescriptor = {sizeof(DEVICE_TRIM_DESCRIPTOR)}; + if (::DeviceIoControl(handle, IOCTL_STORAGE_QUERY_PROPERTY, &queryParameters, + sizeof(queryParameters), &trimDescriptor, + sizeof(trimDescriptor), &bytesRead, nullptr)) { + if (trimDescriptor.TrimEnabled) { + isSSD = true; + } + } + + if (isSSD) { + // Get Seek Penalty + queryParameters.PropertyId = StorageDeviceSeekPenaltyProperty; + bytesRead = 0; + DEVICE_SEEK_PENALTY_DESCRIPTOR seekPenaltyDescriptor = { + sizeof(DEVICE_SEEK_PENALTY_DESCRIPTOR)}; + if (::DeviceIoControl(handle, IOCTL_STORAGE_QUERY_PROPERTY, + &queryParameters, sizeof(queryParameters), + &seekPenaltyDescriptor, sizeof(seekPenaltyDescriptor), + &bytesRead, nullptr)) { + // It is possible that the disk has TrimEnabled, but also + // IncursSeekPenalty; In this case, this is an HDD + if (seekPenaltyDescriptor.IncursSeekPenalty) { + isSSD = false; + } + } + } + + // 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) { + info.model = + reinterpret_cast<char*>(deviceOutput) + deviceOutput->VendorIdOffset; + } + if (deviceOutput->ProductIdOffset) { + info.model += + reinterpret_cast<char*>(deviceOutput) + deviceOutput->ProductIdOffset; + } + info.model.CompressWhitespace(); + if (deviceOutput->ProductRevisionOffset) { + info.revision = reinterpret_cast<char*>(deviceOutput) + + deviceOutput->ProductRevisionOffset; + info.revision.CompressWhitespace(); + } + info.isSSD = isSSD; + free(deviceOutput); + return NS_OK; +} + +static nsresult CollectDiskInfo(nsIFile* greDir, nsIFile* winDir, + nsIFile* profDir, DiskInfo& info) { + nsresult rv = GetFolderDiskInfo(greDir, info.binary); + if (NS_FAILED(rv)) { + return rv; + } + rv = GetFolderDiskInfo(winDir, info.system); + if (NS_FAILED(rv)) { + return rv; + } + return GetFolderDiskInfo(profDir, info.profile); +} + +static nsresult CollectOSInfo(OSInfo& info) { + HKEY installYearHKey; + LONG status = RegOpenKeyExW( + HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", 0, + KEY_READ | KEY_WOW64_64KEY, &installYearHKey); + + if (status != ERROR_SUCCESS) { + return NS_ERROR_UNEXPECTED; + } + + nsAutoRegKey installYearKey(installYearHKey); + + DWORD type = 0; + time_t raw_time = 0; + DWORD time_size = sizeof(time_t); + + status = RegQueryValueExW(installYearHKey, 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; + } + + info.installYear = 1900UL + time.tm_year; + + nsAutoServiceHandle scm( + OpenSCManager(nullptr, SERVICES_ACTIVE_DATABASE, SC_MANAGER_CONNECT)); + + if (!scm) { + return NS_ERROR_UNEXPECTED; + } + + bool superfetchServiceRunning = false; + + // Superfetch was introduced in Windows Vista as a service with the name + // SysMain. The service display name was also renamed to SysMain after Windows + // 10 build 1809. + nsAutoServiceHandle hService(OpenService(scm, L"SysMain", GENERIC_READ)); + + if (hService) { + SERVICE_STATUS superfetchStatus; + LPSERVICE_STATUS pSuperfetchStatus = &superfetchStatus; + + if (!QueryServiceStatus(hService, pSuperfetchStatus)) { + return NS_ERROR_UNEXPECTED; + } + + superfetchServiceRunning = + superfetchStatus.dwCurrentState == SERVICE_RUNNING; + } + + // If the SysMain (Superfetch) service is available, but not configured using + // the defaults, then it's disabled for our purposes, since it's not going to + // be operating as expected. + bool superfetchUsingDefaultParams = true; + bool prefetchUsingDefaultParams = true; + + static const WCHAR prefetchParamsKeyName[] = + L"SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Memory " + L"Management\\PrefetchParameters"; + static const DWORD SUPERFETCH_DEFAULT_PARAM = 3; + static const DWORD PREFETCH_DEFAULT_PARAM = 3; + + HKEY prefetchParamsHKey; + + LONG prefetchParamsStatus = + RegOpenKeyExW(HKEY_LOCAL_MACHINE, prefetchParamsKeyName, 0, + KEY_READ | KEY_WOW64_64KEY, &prefetchParamsHKey); + + if (prefetchParamsStatus == ERROR_SUCCESS) { + DWORD valueSize = sizeof(DWORD); + DWORD superfetchValue = 0; + nsAutoRegKey prefetchParamsKey(prefetchParamsHKey); + LONG superfetchParamStatus = RegQueryValueExW( + prefetchParamsHKey, L"EnableSuperfetch", nullptr, &type, + reinterpret_cast<LPBYTE>(&superfetchValue), &valueSize); + + // If the EnableSuperfetch registry key doesn't exist, then it's using the + // default configuration. + if (superfetchParamStatus == ERROR_SUCCESS && + superfetchValue != SUPERFETCH_DEFAULT_PARAM) { + superfetchUsingDefaultParams = false; + } + + DWORD prefetchValue = 0; + + LONG prefetchParamStatus = RegQueryValueExW( + prefetchParamsHKey, L"EnablePrefetcher", nullptr, &type, + reinterpret_cast<LPBYTE>(&prefetchValue), &valueSize); + + // If the EnablePrefetcher registry key doesn't exist, then we interpret + // that as the Prefetcher being disabled (since Prefetch behaviour when + // the key is not available appears to be undefined). + if (prefetchParamStatus != ERROR_SUCCESS || + prefetchValue != PREFETCH_DEFAULT_PARAM) { + prefetchUsingDefaultParams = false; + } + } + + info.hasSuperfetch = superfetchServiceRunning && superfetchUsingDefaultParams; + info.hasPrefetch = prefetchUsingDefaultParams; + + return NS_OK; +} + +nsresult CollectCountryCode(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, char16ptr_t(aCountryCode.BeginWriting()), + aCountryCode.Length(), 0); + if (!numChars) { + return NS_ERROR_FAILURE; + } + + // numChars includes null terminator + aCountryCode.Truncate(numChars - 1); + return NS_OK; +} + +} // namespace + +# ifndef __MINGW32__ + +static HRESULT EnumWSCProductList( + nsAString& aOutput, mozilla::NotNull<IWSCProductList*> aProdList) { + MOZ_ASSERT(aOutput.IsEmpty()); + + LONG count; + HRESULT hr = aProdList->get_Count(&count); + if (FAILED(hr)) { + return hr; + } + + for (LONG index = 0; index < count; ++index) { + RefPtr<IWscProduct> product; + hr = aProdList->get_Item(index, getter_AddRefs(product)); + if (FAILED(hr)) { + return hr; + } + + WSC_SECURITY_PRODUCT_STATE state; + hr = product->get_ProductState(&state); + if (FAILED(hr)) { + return hr; + } + + // We only care about products that are active + if (state == WSC_SECURITY_PRODUCT_STATE_OFF || + state == WSC_SECURITY_PRODUCT_STATE_SNOOZED) { + continue; + } + + _bstr_t bName; + hr = product->get_ProductName(bName.GetAddress()); + if (FAILED(hr)) { + return hr; + } + + if (!aOutput.IsEmpty()) { + aOutput.AppendLiteral(u";"); + } + + aOutput.Append((wchar_t*)bName, bName.length()); + } + + return S_OK; +} + +static nsresult GetWindowsSecurityCenterInfo(nsAString& aAVInfo, + nsAString& aAntiSpyInfo, + nsAString& aFirewallInfo) { + aAVInfo.Truncate(); + aAntiSpyInfo.Truncate(); + aFirewallInfo.Truncate(); + + if (!XRE_IsParentProcess()) { + return NS_ERROR_NOT_AVAILABLE; + } + + const CLSID clsid = __uuidof(WSCProductList); + const IID iid = __uuidof(IWSCProductList); + + // NB: A separate instance of IWSCProductList is needed for each distinct + // security provider type; MSDN says that we cannot reuse the same object + // and call Initialize() to pave over the previous data. + + WSC_SECURITY_PROVIDER providerTypes[] = {WSC_SECURITY_PROVIDER_ANTIVIRUS, + WSC_SECURITY_PROVIDER_ANTISPYWARE, + WSC_SECURITY_PROVIDER_FIREWALL}; + + // Each output must match the corresponding entry in providerTypes. + nsAString* outputs[] = {&aAVInfo, &aAntiSpyInfo, &aFirewallInfo}; + + static_assert( + mozilla::ArrayLength(providerTypes) == mozilla::ArrayLength(outputs), + "Length of providerTypes and outputs arrays must match"); + + for (uint32_t index = 0; index < mozilla::ArrayLength(providerTypes); + ++index) { + RefPtr<IWSCProductList> prodList; + HRESULT hr = ::CoCreateInstance(clsid, nullptr, CLSCTX_INPROC_SERVER, iid, + getter_AddRefs(prodList)); + if (FAILED(hr)) { + return NS_ERROR_NOT_AVAILABLE; + } + + hr = prodList->Initialize(providerTypes[index]); + if (FAILED(hr)) { + return NS_ERROR_UNEXPECTED; + } + + hr = EnumWSCProductList(*outputs[index], + mozilla::WrapNotNull(prodList.get())); + if (FAILED(hr)) { + return NS_ERROR_UNEXPECTED; + } + } + + return NS_OK; +} + +# endif // __MINGW32__ + +#endif // defined(XP_WIN) + +#ifdef XP_MACOSX +static nsresult GetAppleModelId(nsAutoCString& aModelId) { + size_t numChars = 0; + size_t result = sysctlbyname("hw.model", nullptr, &numChars, nullptr, 0); + if (result != 0 || !numChars) { + return NS_ERROR_FAILURE; + } + aModelId.SetLength(numChars); + result = + sysctlbyname("hw.model", aModelId.BeginWriting(), &numChars, nullptr, 0); + if (result != 0) { + return NS_ERROR_FAILURE; + } + // numChars includes null terminator + aModelId.Truncate(numChars - 1); + return NS_OK; +} + +static nsresult ProcessIsRosettaTranslated(bool& isRosetta) { +# if defined(__aarch64__) + // There is no need to call sysctlbyname() if we are running as arm64. + isRosetta = false; +# else + int ret = 0; + size_t size = sizeof(ret); + if (sysctlbyname("sysctl.proc_translated", &ret, &size, NULL, 0) == -1) { + if (errno != ENOENT) { + fprintf(stderr, "Failed to check for translation environment\n"); + } + isRosetta = false; + } else { + isRosetta = (ret == 1); + } +# endif + return NS_OK; +} +#endif + +using namespace mozilla; + +nsSystemInfo::nsSystemInfo() = default; + +nsSystemInfo::~nsSystemInfo() = default; + +// 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}, + {"hasAES", mozilla::supports_aes}, + // ARM-specific bits. + {"hasEDSP", mozilla::supports_edsp}, + {"hasARMv6", mozilla::supports_armv6}, + {"hasARMv7", mozilla::supports_armv7}, + {"hasNEON", mozilla::supports_neon}}; + +nsresult CollectProcessInfo(ProcessInfo& info) { + nsAutoCString cpuVendor; + nsAutoCString cpuName; + 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) + // IsWow64Process2 is only available on Windows 10+, so we have to dynamically + // check for its existence. + typedef BOOL(WINAPI * LPFN_IWP2)(HANDLE, USHORT*, USHORT*); + LPFN_IWP2 iwp2 = reinterpret_cast<LPFN_IWP2>( + GetProcAddress(GetModuleHandle(L"kernel32"), "IsWow64Process2")); + BOOL isWow64 = FALSE; + USHORT processMachine = IMAGE_FILE_MACHINE_UNKNOWN; + USHORT nativeMachine = IMAGE_FILE_MACHINE_UNKNOWN; + BOOL gotWow64Value; + if (iwp2) { + gotWow64Value = iwp2(GetCurrentProcess(), &processMachine, &nativeMachine); + if (gotWow64Value) { + isWow64 = (processMachine != IMAGE_FILE_MACHINE_UNKNOWN); + } + } else { + gotWow64Value = IsWow64Process(GetCurrentProcess(), &isWow64); + // The function only indicates a WOW64 environment if it's 32-bit x86 + // running on x86-64, so emulate what IsWow64Process2 would have given. + if (gotWow64Value && isWow64) { + processMachine = IMAGE_FILE_MACHINE_I386; + nativeMachine = IMAGE_FILE_MACHINE_AMD64; + } + } + NS_WARNING_ASSERTION(gotWow64Value, "IsWow64Process failed"); + if (gotWow64Value) { + // Set this always, even for the x86-on-arm64 case. + info.isWow64 = !!isWow64; + // Additional information if we're running x86-on-arm64 + info.isWowARM64 = (processMachine == IMAGE_FILE_MACHINE_I386 && + nativeMachine == IMAGE_FILE_MACHINE_ARM64); + } + + // S Mode + +# ifndef __MINGW32__ + // WindowsIntegrityPolicy is only available on newer versions + // of Windows 10, so there's no point in trying to check this + // on earlier versions. We know GetActivationFactory crashes on + // Windows 7 when trying to retrieve this class, and may also + // crash on very old versions of Windows 10. + if (IsWin10Sep2018UpdateOrLater()) { + ComPtr<IWindowsIntegrityPolicyStatics> wip; + HRESULT hr = GetActivationFactory( + HStringReference( + RuntimeClass_Windows_System_Profile_WindowsIntegrityPolicy) + .Get(), + &wip); + if (SUCCEEDED(hr)) { + // info.isWindowsSMode ends up true if Windows is in S mode, otherwise + // false + // https://docs.microsoft.com/en-us/uwp/api/windows.system.profile.windowsintegritypolicy.isenabled?view=winrt-22000 + hr = wip->get_IsEnabled(&info.isWindowsSMode); + NS_WARNING_ASSERTION(SUCCEEDED(hr), + "WindowsIntegrityPolicy.IsEnabled failed"); + } + } +# endif // __MINGW32__ + + // 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); + } + + // 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. + // The expected string size is 48 characters or less. + wchar_t cpuNameStr[64 + 1]; + len = sizeof(cpuNameStr) - 2; + if (RegQueryValueExW(key, L"ProcessorNameString", 0, &vtype, + reinterpret_cast<LPBYTE>(cpuNameStr), + &len) == ERROR_SUCCESS && + vtype == REG_SZ && len % 2 == 0 && len > 1) { + cpuNameStr[len / 2] = 0; // In case it isn't null terminated + CopyUTF16toUTF8(nsDependentString(cpuNameStr), cpuName); + } + + 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; + } + + if (!sysctlbyname("machdep.cpu.brand_string", NULL, &len, NULL, 0)) { + char* cpuNameStr = new char[len]; + if (!sysctlbyname("machdep.cpu.brand_string", cpuNameStr, &len, NULL, 0)) { + cpuName = cpuNameStr; + } + delete[] cpuNameStr; + } + + 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 + // from /proc/cpuinfo file + { + std::map<nsCString, nsCString> keyValuePairs; + SimpleParseKeyValuePairs("/proc/cpuinfo", keyValuePairs); + + // cpuVendor from "vendor_id" + info.cpuVendor.Assign(keyValuePairs["vendor_id"_ns]); + + // cpuName from "model name" + info.cpuName.Assign(keyValuePairs["model name"_ns]); + + { + // cpuFamily from "cpu family" + Tokenizer::Token t; + Tokenizer p(keyValuePairs["cpu family"_ns]); + if (p.Next(t) && t.Type() == Tokenizer::TOKEN_INTEGER && + t.AsInteger() <= INT32_MAX) { + cpuFamily = static_cast<int32_t>(t.AsInteger()); + } + } + + { + // cpuModel from "model" + Tokenizer::Token t; + Tokenizer p(keyValuePairs["model"_ns]); + if (p.Next(t) && t.Type() == Tokenizer::TOKEN_INTEGER && + t.AsInteger() <= INT32_MAX) { + cpuModel = static_cast<int32_t>(t.AsInteger()); + } + } + + { + // cpuStepping from "stepping" + Tokenizer::Token t; + Tokenizer p(keyValuePairs["stepping"_ns]); + if (p.Next(t) && t.Type() == Tokenizer::TOKEN_INTEGER && + t.AsInteger() <= INT32_MAX) { + cpuStepping = static_cast<int32_t>(t.AsInteger()); + } + } + + { + // physicalCPUs from "cpu cores" + Tokenizer::Token t; + Tokenizer p(keyValuePairs["cpu cores"_ns]); + if (p.Next(t) && t.Type() == Tokenizer::TOKEN_INTEGER && + t.AsInteger() <= INT32_MAX) { + physicalCPUs = static_cast<int32_t>(t.AsInteger()); + } + } + } + + { + // 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<int32_t>(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<int32_t>(t.AsInteger()); + } + } + } + + { + // Get cacheSizeL3 from yet another file + std::ifstream input("/sys/devices/system/cpu/cpu0/cache/index3/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) { + cacheSizeL3 = static_cast<int32_t>(t.AsInteger()); + } + } + } + + info.cpuCount = PR_GetNumberOfProcessors(); +#else + info.cpuCount = PR_GetNumberOfProcessors(); +#endif + + if (cpuSpeed >= 0) { + info.cpuSpeed = cpuSpeed; + } else { + info.cpuSpeed = 0; + } + if (!cpuVendor.IsEmpty()) { + info.cpuVendor = cpuVendor; + } + if (!cpuName.IsEmpty()) { + info.cpuName = cpuName; + } + if (cpuFamily >= 0) { + info.cpuFamily = cpuFamily; + } + if (cpuModel >= 0) { + info.cpuModel = cpuModel; + } + if (cpuStepping >= 0) { + info.cpuStepping = cpuStepping; + } + + if (logicalCPUs >= 0) { + info.cpuCount = logicalCPUs; + } + if (physicalCPUs >= 0) { + info.cpuCores = physicalCPUs; + } + + if (cacheSizeL2 >= 0) { + info.l2cacheKB = cacheSizeL2; + } + if (cacheSizeL3 >= 0) { + info.l3cacheKB = cacheSizeL3; + } + + return NS_OK; +} + +#if defined(__MINGW32__) +WINBASEAPI +BOOL WINAPI IsUserCetAvailableInEnvironment(_In_ DWORD UserCetEnvironment); + +# define USER_CET_ENVIRONMENT_WIN32_PROCESS 0x00000000 +#endif + +nsresult nsSystemInfo::Init() { + // check that it is called from the main thread on all platforms. + MOZ_ASSERT(NS_IsMainThread()); + + nsresult rv; + + static const struct { + PRSysInfo cmd; + const char* name; + } items[] = {{PR_SI_SYSNAME, "name"}, + {PR_SI_ARCHITECTURE, "arch"}, + {PR_SI_RELEASE, "version"}, + {PR_SI_RELEASE_BUILD, "build"}}; + + 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"); + } + } + + SetPropertyAsBool(u"isPackagedApp"_ns, false); + + // Additional informations not available through PR_GetSystemInfo. + SetInt32Property(u"pagesize"_ns, PR_GetPageSize()); + SetInt32Property(u"pageshift"_ns, PR_GetPageShift()); + SetInt32Property(u"memmapalign"_ns, PR_GetMemMapAlignment()); + SetUint64Property(u"memsize"_ns, PR_GetPhysicalMemorySize()); + SetUint32Property(u"umask"_ns, nsSystemInfo::gUserUmask); + +#ifdef HAVE_64BIT_BUILD + SetUint32Property(u"archbits"_ns, 64); +#else + SetUint32Property(u"archbits"_ns, 32); +#endif + + uint64_t virtualMem = 0; + +#if defined(XP_WIN) + // Virtual memory: + MEMORYSTATUSEX memStat; + memStat.dwLength = sizeof(memStat); + if (GlobalMemoryStatusEx(&memStat)) { + virtualMem = memStat.ullTotalVirtual; + } +#endif + if (virtualMem) SetUint64Property(u"virtualmemsize"_ns, virtualMem); + + 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 isMinGW = +# ifdef __MINGW32__ + true; +# else + false; +# endif + rv = SetPropertyAsBool(u"isMinGW"_ns, !!isMinGW); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + boolean hasPackageIdentity = widget::WinUtils::HasPackageIdentity(); + + rv = SetPropertyAsBool(u"hasWinPackageId"_ns, hasPackageIdentity); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = SetPropertyAsAString(u"winPackageFamilyName"_ns, + widget::WinUtils::GetPackageFamilyName()); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = SetPropertyAsBool(u"isPackagedApp"_ns, hasPackageIdentity); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + +# ifndef __MINGW32__ + nsAutoString avInfo, antiSpyInfo, firewallInfo; + if (NS_SUCCEEDED( + GetWindowsSecurityCenterInfo(avInfo, antiSpyInfo, firewallInfo))) { + if (!avInfo.IsEmpty()) { + rv = SetPropertyAsAString(u"registeredAntiVirus"_ns, avInfo); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + if (!antiSpyInfo.IsEmpty()) { + rv = SetPropertyAsAString(u"registeredAntiSpyware"_ns, antiSpyInfo); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + if (!firewallInfo.IsEmpty()) { + rv = SetPropertyAsAString(u"registeredFirewall"_ns, firewallInfo); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + } +# endif // __MINGW32__ + + mozilla::DynamicallyLinkedFunctionPtr< + decltype(&IsUserCetAvailableInEnvironment)> + isUserCetAvailable(L"api-ms-win-core-sysinfo-l1-2-6.dll", + "IsUserCetAvailableInEnvironment"); + bool hasUserCET = isUserCetAvailable && + isUserCetAvailable(USER_CET_ENVIRONMENT_WIN32_PROCESS); + rv = SetPropertyAsBool(u"hasUserCET"_ns, hasUserCET); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsString pointerExplanation; + widget::WinUtils::GetPointerExplanation(&pointerExplanation); + rv = SetPropertyAsAString(u"pointingDevices"_ns, pointerExplanation); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + +#endif + +#if defined(XP_MACOSX) + nsAutoCString modelId; + if (NS_SUCCEEDED(GetAppleModelId(modelId))) { + rv = SetPropertyAsACString(u"appleModelId"_ns, modelId); + NS_ENSURE_SUCCESS(rv, rv); + } + bool isRosetta; + if (NS_SUCCEEDED(ProcessIsRosettaTranslated(isRosetta))) { + rv = SetPropertyAsBool(u"rosettaStatus"_ns, isRosetta); + NS_ENSURE_SUCCESS(rv, rv); + } +#endif + + { + nsAutoCString themeInfo; + LookAndFeel::GetThemeInfo(themeInfo); + MOZ_TRY(SetPropertyAsACString(u"osThemeInfo"_ns, themeInfo)); + } + +#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 (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)); + } + +# ifndef MOZ_TSAN + // With TSan, avoid loading libpulse here because we cannot unload it + // afterwards due to restrictions from TSan about unloading libraries + // matched by the suppression list. + 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); + } +# endif + + rv = SetPropertyAsACString(u"secondaryLibrary"_ns, secondaryLibrary); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + rv = SetPropertyAsBool(u"isPackagedApp"_ns, + widget::IsRunningUnderFlatpakOrSnap() || + widget::IsPackagedAppFileExists()); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } +#endif + +#ifdef MOZ_WIDGET_ANDROID + AndroidSystemInfo info; + GetAndroidSystemInfo(&info); + SetupAndroidInfo(info); +#endif + +#if defined(XP_LINUX) && !defined(ANDROID) + nsCString dist, desc, release, codename; + if (widget::lsb::GetLSBRelease(dist, desc, release, codename)) { + SetPropertyAsACString(u"distro"_ns, dist); + SetPropertyAsACString(u"distroVersion"_ns, release); + } +#endif + +#if defined(XP_LINUX) && defined(MOZ_SANDBOX) + SandboxInfo sandInfo = SandboxInfo::Get(); + + SetPropertyAsBool(u"hasSeccompBPF"_ns, + sandInfo.Test(SandboxInfo::kHasSeccompBPF)); + SetPropertyAsBool(u"hasSeccompTSync"_ns, + sandInfo.Test(SandboxInfo::kHasSeccompTSync)); + SetPropertyAsBool(u"hasUserNamespaces"_ns, + sandInfo.Test(SandboxInfo::kHasUserNamespaces)); + SetPropertyAsBool(u"hasPrivilegedUserNamespaces"_ns, + sandInfo.Test(SandboxInfo::kHasPrivilegedUserNamespaces)); + + if (sandInfo.Test(SandboxInfo::kEnabledForContent)) { + SetPropertyAsBool(u"canSandboxContent"_ns, sandInfo.CanSandboxContent()); + } + + if (sandInfo.Test(SandboxInfo::kEnabledForMedia)) { + SetPropertyAsBool(u"canSandboxMedia"_ns, 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. Search for "kDefaultAndroidMajorVersion" in: +// https://source.chromium.org/chromium/chromium/src/+/master:base/system/sys_info_android.cc +# define DEFAULT_ANDROID_VERSION u"10.0.99" + +/* static */ +void nsSystemInfo::GetAndroidSystemInfo(AndroidSystemInfo* aInfo) { + if (!jni::IsAvailable()) { + // called from xpcshell etc. + aInfo->sdk_version() = 0; + return; + } + + jni::String::LocalRef model = java::sdk::Build::MODEL(); + aInfo->device() = model->ToString(); + + jni::String::LocalRef manufacturer = + mozilla::java::sdk::Build::MANUFACTURER(); + aInfo->manufacturer() = manufacturer->ToString(); + + jni::String::LocalRef hardware = java::sdk::Build::HARDWARE(); + aInfo->hardware() = hardware->ToString(); + + jni::String::LocalRef release = java::sdk::Build::VERSION::RELEASE(); + nsString str(release->ToString()); + 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() = nsLiteralString(DEFAULT_ANDROID_VERSION); + } else { + aInfo->release_version() = str; + } + + aInfo->sdk_version() = jni::GetAPIVersion(); + aInfo->isTablet() = java::GeckoAppShell::IsTablet(); +} + +void nsSystemInfo::SetupAndroidInfo(const AndroidSystemInfo& aInfo) { + if (!aInfo.device().IsEmpty()) { + SetPropertyAsAString(u"device"_ns, aInfo.device()); + } + if (!aInfo.manufacturer().IsEmpty()) { + SetPropertyAsAString(u"manufacturer"_ns, aInfo.manufacturer()); + } + if (!aInfo.release_version().IsEmpty()) { + SetPropertyAsAString(u"release_version"_ns, aInfo.release_version()); + } + SetPropertyAsBool(u"tablet"_ns, 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(u"version"_ns, str); + if (NS_SUCCEEDED(rv)) { + SetPropertyAsAString(u"kernel_version"_ns, str); + } + // When JNI is not available (eg. in xpcshell tests), sdk_version is 0. + if (aInfo.sdk_version() != 0) { + if (!aInfo.hardware().IsEmpty()) { + SetPropertyAsAString(u"hardware"_ns, aInfo.hardware()); + } + SetPropertyAsInt32(u"version"_ns, aInfo.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"); + } +} + +#ifdef XP_WIN + +static bool GetJSObjForDiskInfo(JSContext* aCx, JS::Handle<JSObject*> aParent, + const FolderDiskInfo& info, + const char* propName) { + JS::Rooted<JSObject*> jsInfo(aCx, JS_NewPlainObject(aCx)); + if (!jsInfo) { + return false; + } + + JSString* strModel = + JS_NewStringCopyN(aCx, info.model.get(), info.model.Length()); + if (!strModel) { + return false; + } + JS::Rooted<JS::Value> valModel(aCx, JS::StringValue(strModel)); + if (!JS_SetProperty(aCx, jsInfo, "model", valModel)) { + return false; + } + + JSString* strRevision = + JS_NewStringCopyN(aCx, info.revision.get(), info.revision.Length()); + if (!strRevision) { + return false; + } + JS::Rooted<JS::Value> valRevision(aCx, JS::StringValue(strRevision)); + if (!JS_SetProperty(aCx, jsInfo, "revision", valRevision)) { + return false; + } + + JSString* strSSD = JS_NewStringCopyZ(aCx, info.isSSD ? "SSD" : "HDD"); + if (!strSSD) { + return false; + } + JS::Rooted<JS::Value> valSSD(aCx, JS::StringValue(strSSD)); + if (!JS_SetProperty(aCx, jsInfo, "type", valSSD)) { + return false; + } + + JS::Rooted<JS::Value> val(aCx, JS::ObjectValue(*jsInfo)); + return JS_SetProperty(aCx, aParent, propName, val); +} + +JSObject* GetJSObjForOSInfo(JSContext* aCx, const OSInfo& info) { + JS::Rooted<JSObject*> jsInfo(aCx, JS_NewPlainObject(aCx)); + + JS::Rooted<JS::Value> valInstallYear(aCx, JS::Int32Value(info.installYear)); + JS_SetProperty(aCx, jsInfo, "installYear", valInstallYear); + + JS::Rooted<JS::Value> valHasSuperfetch(aCx, + JS::BooleanValue(info.hasSuperfetch)); + JS_SetProperty(aCx, jsInfo, "hasSuperfetch", valHasSuperfetch); + + JS::Rooted<JS::Value> valHasPrefetch(aCx, JS::BooleanValue(info.hasPrefetch)); + JS_SetProperty(aCx, jsInfo, "hasPrefetch", valHasPrefetch); + + return jsInfo; +} + +#endif + +JSObject* GetJSObjForProcessInfo(JSContext* aCx, const ProcessInfo& info) { + JS::Rooted<JSObject*> jsInfo(aCx, JS_NewPlainObject(aCx)); + +#if defined(XP_WIN) + JS::Rooted<JS::Value> valisWow64(aCx, JS::BooleanValue(info.isWow64)); + JS_SetProperty(aCx, jsInfo, "isWow64", valisWow64); + + JS::Rooted<JS::Value> valisWowARM64(aCx, JS::BooleanValue(info.isWowARM64)); + JS_SetProperty(aCx, jsInfo, "isWowARM64", valisWowARM64); + + JS::Rooted<JS::Value> valisWindowsSMode( + aCx, JS::BooleanValue(info.isWindowsSMode)); + JS_SetProperty(aCx, jsInfo, "isWindowsSMode", valisWindowsSMode); +#endif + + JS::Rooted<JS::Value> valCountInfo(aCx, JS::Int32Value(info.cpuCount)); + JS_SetProperty(aCx, jsInfo, "count", valCountInfo); + + JS::Rooted<JS::Value> valCoreInfo(aCx, JS::Int32Value(info.cpuCores)); + JS_SetProperty(aCx, jsInfo, "cores", valCoreInfo); + + JSString* strVendor = + JS_NewStringCopyN(aCx, info.cpuVendor.get(), info.cpuVendor.Length()); + JS::Rooted<JS::Value> valVendor(aCx, JS::StringValue(strVendor)); + JS_SetProperty(aCx, jsInfo, "vendor", valVendor); + + JSString* strName = + JS_NewStringCopyN(aCx, info.cpuName.get(), info.cpuName.Length()); + JS::Rooted<JS::Value> valName(aCx, JS::StringValue(strName)); + JS_SetProperty(aCx, jsInfo, "name", valName); + + JS::Rooted<JS::Value> valFamilyInfo(aCx, JS::Int32Value(info.cpuFamily)); + JS_SetProperty(aCx, jsInfo, "family", valFamilyInfo); + + JS::Rooted<JS::Value> valModelInfo(aCx, JS::Int32Value(info.cpuModel)); + JS_SetProperty(aCx, jsInfo, "model", valModelInfo); + + JS::Rooted<JS::Value> valSteppingInfo(aCx, JS::Int32Value(info.cpuStepping)); + JS_SetProperty(aCx, jsInfo, "stepping", valSteppingInfo); + + JS::Rooted<JS::Value> valL2CacheInfo(aCx, JS::Int32Value(info.l2cacheKB)); + JS_SetProperty(aCx, jsInfo, "l2cacheKB", valL2CacheInfo); + + JS::Rooted<JS::Value> valL3CacheInfo(aCx, JS::Int32Value(info.l3cacheKB)); + JS_SetProperty(aCx, jsInfo, "l3cacheKB", valL3CacheInfo); + + JS::Rooted<JS::Value> valSpeedInfo(aCx, JS::Int32Value(info.cpuSpeed)); + JS_SetProperty(aCx, jsInfo, "speedMHz", valSpeedInfo); + + return jsInfo; +} + +RefPtr<nsISerialEventTarget> nsSystemInfo::GetBackgroundTarget() { + if (!mBackgroundET) { + MOZ_ALWAYS_SUCCEEDS(NS_CreateBackgroundTaskQueue( + "SystemInfoThread", getter_AddRefs(mBackgroundET))); + } + return mBackgroundET; +} + +NS_IMETHODIMP +nsSystemInfo::GetOsInfo(JSContext* aCx, Promise** aResult) { + NS_ENSURE_ARG_POINTER(aResult); + *aResult = nullptr; + if (!XRE_IsParentProcess()) { + return NS_ERROR_FAILURE; + } +#if defined(XP_WIN) + nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx); + if (NS_WARN_IF(!global)) { + return NS_ERROR_FAILURE; + } + + ErrorResult erv; + RefPtr<Promise> promise = Promise::Create(global, erv); + if (NS_WARN_IF(erv.Failed())) { + return erv.StealNSResult(); + } + + if (!mOSInfoPromise) { + RefPtr<nsISerialEventTarget> backgroundET = GetBackgroundTarget(); + + mOSInfoPromise = InvokeAsync(backgroundET, __func__, []() { + OSInfo info; + nsresult rv = CollectOSInfo(info); + if (NS_SUCCEEDED(rv)) { + return OSInfoPromise::CreateAndResolve(info, __func__); + } + return OSInfoPromise::CreateAndReject(rv, __func__); + }); + }; + + // Chain the new promise to the extant mozpromise + RefPtr<Promise> capturedPromise = promise; + mOSInfoPromise->Then( + GetMainThreadSerialEventTarget(), __func__, + [capturedPromise](const OSInfo& info) { + AutoJSAPI jsapi; + if (NS_WARN_IF(!jsapi.Init(capturedPromise->GetGlobalObject()))) { + capturedPromise->MaybeReject(NS_ERROR_UNEXPECTED); + return; + } + JSContext* cx = jsapi.cx(); + JS::Rooted<JS::Value> val( + cx, JS::ObjectValue(*GetJSObjForOSInfo(cx, info))); + capturedPromise->MaybeResolve(val); + }, + [capturedPromise](const nsresult rv) { + // Resolve with null when installYear is not available from the system + capturedPromise->MaybeResolve(JS::NullHandleValue); + }); + + promise.forget(aResult); +#endif + return NS_OK; +} + +NS_IMETHODIMP +nsSystemInfo::GetDiskInfo(JSContext* aCx, Promise** aResult) { + NS_ENSURE_ARG_POINTER(aResult); + *aResult = nullptr; + if (!XRE_IsParentProcess()) { + return NS_ERROR_FAILURE; + } +#ifdef XP_WIN + nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx); + if (NS_WARN_IF(!global)) { + return NS_ERROR_FAILURE; + } + ErrorResult erv; + RefPtr<Promise> promise = Promise::Create(global, erv); + if (NS_WARN_IF(erv.Failed())) { + return erv.StealNSResult(); + } + + if (!mDiskInfoPromise) { + RefPtr<nsISerialEventTarget> backgroundET = GetBackgroundTarget(); + nsCOMPtr<nsIFile> greDir; + nsCOMPtr<nsIFile> winDir; + nsCOMPtr<nsIFile> profDir; + nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(greDir)); + if (NS_FAILED(rv)) { + return rv; + } + rv = NS_GetSpecialDirectory(NS_WIN_WINDOWS_DIR, getter_AddRefs(winDir)); + if (NS_FAILED(rv)) { + return rv; + } + rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, + getter_AddRefs(profDir)); + if (NS_FAILED(rv)) { + return rv; + } + + mDiskInfoPromise = + InvokeAsync(backgroundET, __func__, [greDir, winDir, profDir]() { + DiskInfo info; + nsresult rv = CollectDiskInfo(greDir, winDir, profDir, info); + if (NS_SUCCEEDED(rv)) { + return DiskInfoPromise::CreateAndResolve(info, __func__); + } + return DiskInfoPromise::CreateAndReject(rv, __func__); + }); + } + + // Chain the new promise to the extant mozpromise. + RefPtr<Promise> capturedPromise = promise; + mDiskInfoPromise->Then( + GetMainThreadSerialEventTarget(), __func__, + [capturedPromise](const DiskInfo& info) { + AutoJSAPI jsapi; + if (NS_WARN_IF(!jsapi.Init(capturedPromise->GetGlobalObject()))) { + capturedPromise->MaybeReject(NS_ERROR_UNEXPECTED); + return; + } + JSContext* cx = jsapi.cx(); + JS::Rooted<JSObject*> jsInfo(cx, JS_NewPlainObject(cx)); + // Store data in the rv: + bool succeededSettingAllObjects = + jsInfo && GetJSObjForDiskInfo(cx, jsInfo, info.binary, "binary") && + GetJSObjForDiskInfo(cx, jsInfo, info.profile, "profile") && + GetJSObjForDiskInfo(cx, jsInfo, info.system, "system"); + // The above can fail due to OOM + if (!succeededSettingAllObjects) { + JS_ClearPendingException(cx); + capturedPromise->MaybeReject(NS_ERROR_FAILURE); + return; + } + + JS::Rooted<JS::Value> val(cx, JS::ObjectValue(*jsInfo)); + capturedPromise->MaybeResolve(val); + }, + [capturedPromise](const nsresult rv) { + capturedPromise->MaybeReject(rv); + }); + + promise.forget(aResult); +#endif + return NS_OK; +} + +NS_IMPL_ISUPPORTS_INHERITED(nsSystemInfo, nsHashPropertyBag, nsISystemInfo) + +NS_IMETHODIMP +nsSystemInfo::GetCountryCode(JSContext* aCx, Promise** aResult) { + NS_ENSURE_ARG_POINTER(aResult); + *aResult = nullptr; + + if (!XRE_IsParentProcess()) { + return NS_ERROR_FAILURE; + } +#if defined(XP_MACOSX) || defined(XP_WIN) + nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx); + if (NS_WARN_IF(!global)) { + return NS_ERROR_FAILURE; + } + + ErrorResult erv; + RefPtr<Promise> promise = Promise::Create(global, erv); + if (NS_WARN_IF(erv.Failed())) { + return erv.StealNSResult(); + } + + if (!mCountryCodePromise) { + RefPtr<nsISerialEventTarget> backgroundET = GetBackgroundTarget(); + + mCountryCodePromise = InvokeAsync(backgroundET, __func__, []() { + nsAutoString countryCode; +# ifdef XP_MACOSX + nsresult rv = GetSelectedCityInfo(countryCode); +# endif +# ifdef XP_WIN + nsresult rv = CollectCountryCode(countryCode); +# endif + + if (NS_SUCCEEDED(rv)) { + return CountryCodePromise::CreateAndResolve(countryCode, __func__); + } + return CountryCodePromise::CreateAndReject(rv, __func__); + }); + } + + RefPtr<Promise> capturedPromise = promise; + mCountryCodePromise->Then( + GetMainThreadSerialEventTarget(), __func__, + [capturedPromise](const nsString& countryCode) { + AutoJSAPI jsapi; + if (NS_WARN_IF(!jsapi.Init(capturedPromise->GetGlobalObject()))) { + capturedPromise->MaybeReject(NS_ERROR_UNEXPECTED); + return; + } + JSContext* cx = jsapi.cx(); + JS::Rooted<JSString*> jsCountryCode( + cx, JS_NewUCStringCopyZ(cx, countryCode.get())); + + JS::Rooted<JS::Value> val(cx, JS::StringValue(jsCountryCode)); + capturedPromise->MaybeResolve(val); + }, + [capturedPromise](const nsresult rv) { + // Resolve with null when countryCode is not available from the system + capturedPromise->MaybeResolve(JS::NullHandleValue); + }); + + promise.forget(aResult); +#endif + return NS_OK; +} + +NS_IMETHODIMP +nsSystemInfo::GetProcessInfo(JSContext* aCx, Promise** aResult) { + NS_ENSURE_ARG_POINTER(aResult); + *aResult = nullptr; + + if (!XRE_IsParentProcess()) { + return NS_ERROR_FAILURE; + } + + nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx); + if (NS_WARN_IF(!global)) { + return NS_ERROR_FAILURE; + } + + ErrorResult erv; + RefPtr<Promise> promise = Promise::Create(global, erv); + if (NS_WARN_IF(erv.Failed())) { + return erv.StealNSResult(); + } + + if (!mProcessInfoPromise) { + RefPtr<nsISerialEventTarget> backgroundET = GetBackgroundTarget(); + + mProcessInfoPromise = InvokeAsync(backgroundET, __func__, []() { + ProcessInfo info; + nsresult rv = CollectProcessInfo(info); + if (NS_SUCCEEDED(rv)) { + return ProcessInfoPromise::CreateAndResolve(info, __func__); + } + return ProcessInfoPromise::CreateAndReject(rv, __func__); + }); + }; + + // Chain the new promise to the extant mozpromise + RefPtr<Promise> capturedPromise = promise; + mProcessInfoPromise->Then( + GetMainThreadSerialEventTarget(), __func__, + [capturedPromise](const ProcessInfo& info) { + AutoJSAPI jsapi; + if (NS_WARN_IF(!jsapi.Init(capturedPromise->GetGlobalObject()))) { + capturedPromise->MaybeReject(NS_ERROR_UNEXPECTED); + return; + } + JSContext* cx = jsapi.cx(); + JS::Rooted<JS::Value> val( + cx, JS::ObjectValue(*GetJSObjForProcessInfo(cx, info))); + capturedPromise->MaybeResolve(val); + }, + [capturedPromise](const nsresult rv) { + // Resolve with null when installYear is not available from the system + capturedPromise->MaybeResolve(JS::NullHandleValue); + }); + + promise.forget(aResult); + + return NS_OK; +} diff --git a/xpcom/base/nsSystemInfo.h b/xpcom/base/nsSystemInfo.h new file mode 100644 index 0000000000..9d47b5d4f6 --- /dev/null +++ b/xpcom/base/nsSystemInfo.h @@ -0,0 +1,130 @@ +/* -*- 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" +#include "nsISystemInfo.h" +#include "mozilla/MozPromise.h" + +#ifdef MOZ_WIDGET_ANDROID +# include "mozilla/dom/PContent.h" +#endif // MOZ_WIDGET_ANDROID + +#if defined(XP_WIN) +# include <inspectable.h> + +// The UUID comes from winrt/windows.system.profile.idl +// in the Windows SDK +MIDL_INTERFACE("7D1D81DB-8D63-4789-9EA5-DDCF65A94F3C") +IWindowsIntegrityPolicyStatics : public IInspectable { + public: + virtual HRESULT STDMETHODCALLTYPE get_IsEnabled(bool* value) = 0; +}; +#endif + +class nsISerialEventTarget; + +struct FolderDiskInfo { + nsCString model; + nsCString revision; + bool isSSD; +}; + +struct DiskInfo { + FolderDiskInfo binary; + FolderDiskInfo profile; + FolderDiskInfo system; +}; + +struct OSInfo { + uint32_t installYear; + bool hasSuperfetch; + bool hasPrefetch; +}; + +struct ProcessInfo { + bool isWow64 = false; + bool isWowARM64 = false; + // Whether or not the system is Windows 10 or 11 in S Mode. + // S Mode existed prior to us being able to query it, so this + // is unreliable on Windows versions prior to 1810. + bool isWindowsSMode = false; + int32_t cpuCount = 0; + int32_t cpuCores = 0; + nsCString cpuVendor; + nsCString cpuName; + int32_t cpuFamily = 0; + int32_t cpuModel = 0; + int32_t cpuStepping = 0; + int32_t l2cacheKB = 0; + int32_t l3cacheKB = 0; + int32_t cpuSpeed = 0; +}; + +typedef mozilla::MozPromise<DiskInfo, nsresult, /* IsExclusive */ false> + DiskInfoPromise; + +typedef mozilla::MozPromise<nsAutoString, nsresult, /* IsExclusive */ false> + CountryCodePromise; + +typedef mozilla::MozPromise<OSInfo, nsresult, /* IsExclusive */ false> + OSInfoPromise; + +typedef mozilla::MozPromise<ProcessInfo, nsresult, /* IsExclusive */ false> + ProcessInfoPromise; + +// Synchronous info collection, avoid calling it from the main thread, consider +// using the promise-based `nsISystemInfo::GetProcessInfo()` instead. +// Note that only known fields will be written. +nsresult CollectProcessInfo(ProcessInfo& info); + +class nsSystemInfo final : public nsISystemInfo, public nsHashPropertyBag { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSISYSTEMINFO + + nsSystemInfo(); + + nsresult Init(); + + // Slot for NS_InitXPCOM to pass information to nsSystemInfo::Init. + // See comments above the variable definition and in NS_InitXPCOM. + 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(); + + RefPtr<DiskInfoPromise> mDiskInfoPromise; + RefPtr<CountryCodePromise> mCountryCodePromise; + RefPtr<OSInfoPromise> mOSInfoPromise; + RefPtr<ProcessInfoPromise> mProcessInfoPromise; + RefPtr<nsISerialEventTarget> mBackgroundET; + RefPtr<nsISerialEventTarget> GetBackgroundTarget(); +}; + +#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 0000000000..41de68eb31 --- /dev/null +++ b/xpcom/base/nsTraceRefcnt.cpp @@ -0,0 +1,1219 @@ +/* -*- 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 "base/process_util.h" +#include "mozilla/Attributes.h" +#include "mozilla/AutoRestore.h" +#include "mozilla/CycleCollectedJSContext.h" +#include "mozilla/IntegerPrintfMacros.h" +#include "mozilla/Path.h" +#include "mozilla/Sprintf.h" +#include "mozilla/StaticPtr.h" +#include "nsXPCOMPrivate.h" +#include "nscore.h" +#include "nsClassHashtable.h" +#include "nsContentUtils.h" +#include "nsISupports.h" +#include "nsHashKeys.h" +#include "nsPrintfCString.h" +#include "nsTArray.h" +#include "nsTHashtable.h" +#include "prenv.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 <io.h> +# 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 "mozilla/UniquePtr.h" + +#include <string> +#include <vector> + +#ifdef HAVE_DLOPEN +# include <dlfcn.h> +#endif + +#ifdef MOZ_DMD +# include "nsMemoryInfoDumper.h" +#endif + +// dynamic_cast<void*> is not supported on Windows without RTTI. +#ifndef _WIN32 +# define HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR +#endif + +//////////////////////////////////////////////////////////////////////////////// + +#include "prthread.h" + +class MOZ_CAPABILITY("mutex") TraceLogMutex + : private mozilla::detail::MutexImpl { + public: + explicit TraceLogMutex() = default; + + private: + friend class AutoTraceLogLock; + + void Lock() MOZ_CAPABILITY_ACQUIRE() { ::mozilla::detail::MutexImpl::lock(); } + void Unlock() MOZ_CAPABILITY_RELEASE() { + ::mozilla::detail::MutexImpl::unlock(); + } +}; + +static TraceLogMutex gTraceLog; + +class MOZ_RAII AutoTraceLogLock { + public: + explicit AutoTraceLogLock(TraceLogMutex& aMutex) : mMutex(aMutex) { + mMutex.Lock(); + } + + AutoTraceLogLock(const AutoTraceLogLock&) = delete; + AutoTraceLogLock& operator=(const AutoTraceLogLock&) = delete; + AutoTraceLogLock(AutoTraceLogLock&&) = delete; + AutoTraceLogLock& operator=(AutoTraceLogLock&&) = delete; + + ~AutoTraceLogLock() { mMutex.Unlock(); } + + private: + TraceLogMutex& mMutex; +}; + +class BloatEntry; +struct SerialNumberRecord; + +using mozilla::AutoRestore; +using mozilla::CodeAddressService; +using mozilla::CycleCollectedJSContext; +using mozilla::StaticAutoPtr; + +using BloatHash = nsClassHashtable<nsDepCharHashKey, BloatEntry>; +using CharPtrSet = nsTHashtable<nsCharPtrHashKey>; +using IntPtrSet = nsTHashtable<IntPtrHashKey>; +using SerialHash = nsClassHashtable<nsVoidPtrHashKey, SerialNumberRecord>; + +static StaticAutoPtr<BloatHash> gBloatView; +static StaticAutoPtr<CharPtrSet> gTypesToLog; +static StaticAutoPtr<IntPtrSet> gObjectsToLog; +static StaticAutoPtr<SerialHash> gSerialNumbers; + +static intptr_t gNextSerialNumber; +static bool gDumpedStatistics = false; +static bool gLogJSStacks = 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, + const void* aFirstFramePC); + +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; + mozilla::UniquePtr<char[]> jsStack; + + void SaveJSStack() { + // If this thread isn't running JS, there's nothing to do. + if (!CycleCollectedJSContext::Get()) { + return; + } + + if (!nsContentUtils::IsInitialized()) { + return; + } + + JSContext* cx = nsContentUtils::GetCurrentJSContext(); + if (!cx) { + return; + } + + JS::UniqueChars chars = xpc_PrintJSStack(cx, + /*showArgs=*/false, + /*showLocals=*/false, + /*showThisProps=*/false); + size_t len = strlen(chars.get()); + jsStack = mozilla::MakeUnique<char[]>(len + 1); + memcpy(jsStack.get(), chars.get(), len + 1); + } +}; + +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 void AssertActivityIsLegal(const char* aType, const char* aAction) { + if (gActivityTLS == BAD_TLS_INDEX || PR_GetThreadPrivate(gActivityTLS)) { + char buf[1024]; + SprintfLiteral(buf, "XPCOM object %s %s from static ctor/dtor", aType, + aAction); + + if (PR_GetEnv("MOZ_FATAL_STATIC_XPCOM_CTORS_DTORS")) { + MOZ_CRASH_UNSAFE_PRINTF("%s", buf); + } else { + NS_WARNING(buf); + } + } +} +# define ASSERT_ACTIVITY_IS_LEGAL(type_, action_) \ + do { \ + AssertActivityIsLegal(type_, action_); \ + } while (0) +#else +# define ASSERT_ACTIVITY_IS_LEGAL(type_, action_) \ + do { \ + } while (0) +#endif // DEBUG + +//////////////////////////////////////////////////////////////////////////////// + +mozilla::StaticAutoPtr<CodeAddressService<>> gCodeAddressService; + +//////////////////////////////////////////////////////////////////////////////// + +class BloatEntry { + public: + BloatEntry(const char* aClassName, uint32_t aClassSize) + : mClassSize(aClassSize), mStats() { + MOZ_ASSERT(strlen(aClassName) > 0, "BloatEntry name must be non-empty"); + mClassName = aClassName; + mStats.Clear(); + mTotalLeaked = 0; + } + + ~BloatEntry() = default; + + uint32_t GetClassSize() { return (uint32_t)mClassSize; } + const char* GetClassName() { return mClassName; } + + void Ctor() { mStats.mCreates++; } + + void Dtor() { mStats.mDestroys++; } + + 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_GetProcessTypeString(), getpid()); + if (gLogLeaksOnly && !mStats.HaveLeaks()) { + return false; + } + + // clang-format off + fprintf(aOut, + "\n" \ + " |<----------------Class--------------->|<-----Bytes------>|<----Objects---->|\n" \ + " | | Per-Inst Leaked| Total Rem|\n"); + // clang-format on + + 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: + const char* mClassName; + // mClassSize is stored as a double because of the way we compute the avg + // class size for total bloat. + double mClassSize; + // mTotalLeaked is only used for the TOTAL entry. + int64_t mTotalLeaked; + nsTraceRefcntStats mStats; +}; + +static void EnsureBloatView() { + if (!gBloatView) { + gBloatView = new BloatHash(256); + } +} + +static BloatEntry* GetBloatEntry(const char* aTypeName, + uint32_t aInstanceSize) { + EnsureBloatView(); + BloatEntry* entry = gBloatView->Get(aTypeName); + if (!entry && aInstanceSize > 0) { + entry = gBloatView + ->InsertOrUpdate(aTypeName, mozilla::MakeUnique<BloatEntry>( + aTypeName, aInstanceSize)) + .get(); + } 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. Another possible cause is a runnable with " + "an mName that matches another refcounted class, or two refcounted " + "classes with the same class name in different C++ namespaces."); + } + return entry; +} + +static void DumpSerialNumbers(const SerialHash::ConstIterator& aHashEntry, + FILE* aFd, bool aDumpAsStringBuffer) { + SerialNumberRecord* record = aHashEntry.UserData(); + auto* outputFile = aFd; +#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 (aDumpAsStringBuffer) { + // This output will be wrong if the nsStringBuffer was used to + // store a char16_t string. + auto* buffer = static_cast<const nsStringBuffer*>(aHashEntry.Key()); + nsDependentCString bufferString(static_cast<char*>(buffer->Data())); + fprintf(outputFile, + "Contents of leaked nsStringBuffer with storage size %d as a " + "char*: %s\n", + buffer->StorageSize(), bufferString.get()); + } + + 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); + } + } + + if (gLogJSStacks) { + if (record->jsStack) { + fprintf(outputFile, "JS allocation stack:\n%s\n", record->jsStack.get()); + } else { + fprintf(outputFile, "There is no JS context on the stack.\n"); + } + } +} + +template <> +class nsDefaultComparator<BloatEntry*, BloatEntry*> { + public: + bool Equals(BloatEntry* const& aEntry1, BloatEntry* const& aEntry2) const { + return strcmp(aEntry1->GetClassName(), aEntry2->GetClassName()) == 0; + } + bool LessThan(BloatEntry* const& aEntry1, BloatEntry* const& aEntry2) const { + return strcmp(aEntry1->GetClassName(), aEntry2->GetClassName()) < 0; + } +}; + +nsresult nsTraceRefcnt::DumpStatistics() { + if (!gBloatLog || !gBloatView) { + return NS_ERROR_FAILURE; + } + + AutoTraceLogLock lock(gTraceLog); + + 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); + for (const auto& data : gBloatView->Values()) { + if (nsCRT::strcmp(data->GetClassName(), "TOTAL") != 0) { + data->Total(&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(gBloatView->Count()); + for (const auto& data : gBloatView->Values()) { + entries.AppendElement(data.get()); + } + + 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) { + bool onlyLoggingStringBuffers = gTypesToLog && gTypesToLog->Count() == 1 && + gTypesToLog->Contains("nsStringBuffer"); + + fprintf(gBloatLog, "\nSerial Numbers of Leaked Objects:\n"); + for (auto iter = gSerialNumbers->ConstIter(); !iter.Done(); iter.Next()) { + DumpSerialNumbers(iter, gBloatLog, onlyLoggingStringBuffers); + } + } + + return NS_OK; +} + +void nsTraceRefcnt::ResetStatistics() { + AutoTraceLogLock lock(gTraceLog); + gBloatView = nullptr; +} + +static intptr_t GetSerialNumber(void* aPtr, bool aCreate, void* aFirstFramePC) { + if (!aCreate) { + auto record = gSerialNumbers->Get(aPtr); + return record ? record->serialNumber : 0; + } + + gSerialNumbers->WithEntryHandle(aPtr, [aFirstFramePC](auto&& entry) { + if (entry) { + MOZ_CRASH( + "If an object already has a serial number, we should be destroying " + "it."); + } + + auto& record = entry.Insert(mozilla::MakeUnique<SerialNumberRecord>()); + WalkTheStackSavingLocations(record->allocationStack, aFirstFramePC); + if (gLogJSStacks) { + record->SaveJSStack(); + } + }); + return gNextSerialNumber; +} + +static void RecycleSerialNumberPtr(void* aPtr) { gSerialNumbers->Remove(aPtr); } + +static bool LogThisObj(intptr_t aSerialNumber) { + return gObjectsToLog->Contains(aSerialNumber); +} + +using EnvCharType = mozilla::filesystem::Path::value_type; + +static bool InitLog(const EnvCharType* aEnvVar, const char* aMsg, + FILE** aResult, const char* aProcType) { +#ifdef XP_WIN + // This is gross, I know. + const wchar_t* envvar = reinterpret_cast<const wchar_t*>(aEnvVar); + const char16_t* value = reinterpret_cast<const char16_t*>(::_wgetenv(envvar)); +# define ENVVAR_PRINTF "%S" +#else + const char* envvar = aEnvVar; + const char* value = ::getenv(aEnvVar); +# define ENVVAR_PRINTF "%s" +#endif + + if (value) { + nsTDependentString<EnvCharType> fname(value); + if (fname.EqualsLiteral("1")) { + *aResult = stdout; + fprintf(stdout, "### " ENVVAR_PRINTF " defined -- logging %s to stdout\n", + envvar, aMsg); + return true; + } + if (fname.EqualsLiteral("2")) { + *aResult = stderr; + fprintf(stdout, "### " ENVVAR_PRINTF " defined -- logging %s to stderr\n", + envvar, aMsg); + return true; + } + if (!XRE_IsParentProcess()) { + nsTString<EnvCharType> extension; + extension.AssignLiteral(".log"); + bool hasLogExtension = StringEndsWith(fname, extension); + if (hasLogExtension) { + fname.Cut(fname.Length() - 4, 4); + } + fname.Append('_'); + fname.AppendASCII(aProcType); + fname.AppendLiteral("_pid"); + fname.AppendInt((uint32_t)getpid()); + if (hasLogExtension) { + fname.AppendLiteral(".log"); + } + } +#ifdef XP_WIN + FILE* stream = ::_wfopen(fname.get(), L"wN"); + const wchar_t* fp = (const wchar_t*)fname.get(); +#else + FILE* stream = ::fopen(fname.get(), "w"); + const char* fp = fname.get(); +#endif + if (stream) { + MozillaRegisterDebugFD(fileno(stream)); +#ifdef MOZ_ENABLE_FORKSERVER + base::RegisterForkServerNoCloseFD(fileno(stream)); +#endif + *aResult = stream; + fprintf(stderr, + "### " ENVVAR_PRINTF " defined -- logging %s to " ENVVAR_PRINTF + "\n", + envvar, aMsg, fp); + + return true; + } + + fprintf(stderr, + "### " ENVVAR_PRINTF + " defined -- unable to log %s to " ENVVAR_PRINTF "\n", + envvar, aMsg, fp); + MOZ_ASSERT(false, "Tried and failed to create an XPCOM log"); + +#undef ENVVAR_PRINTF + } + return false; +} + +static void maybeUnregisterAndCloseFile(FILE*& aFile) { + if (!aFile) { + return; + } + + MozillaUnRegisterDebugFILE(aFile); + fclose(aFile); + aFile = nullptr; +} + +static void DoInitTraceLog(const char* aProcType) { +#ifdef XP_WIN +# define ENVVAR(x) u"" x +#else +# define ENVVAR(x) x +#endif + + bool defined = InitLog(ENVVAR("XPCOM_MEM_BLOAT_LOG"), "bloat/leaks", + &gBloatLog, aProcType); + if (!defined) { + gLogLeaksOnly = + InitLog(ENVVAR("XPCOM_MEM_LEAK_LOG"), "leaks", &gBloatLog, aProcType); + } + if (defined || gLogLeaksOnly) { + // Use the same bloat view, if there is one, to keep it consistent + // between the fork server and content processes. + EnsureBloatView(); + } else if (gBloatView) { + nsTraceRefcnt::ResetStatistics(); + } + + InitLog(ENVVAR("XPCOM_MEM_REFCNT_LOG"), "refcounts", &gRefcntsLog, aProcType); + + InitLog(ENVVAR("XPCOM_MEM_ALLOC_LOG"), "new/delete", &gAllocLog, aProcType); + + const char* classes = getenv("XPCOM_MEM_LOG_CLASSES"); + +#ifdef HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR + if (classes) { + InitLog(ENVVAR("XPCOM_MEM_COMPTR_LOG"), "nsCOMPtr", &gCOMPtrLog, aProcType); + } 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 + +#undef ENVVAR + + if (classes) { + // if XPCOM_MEM_LOG_CLASSES was set to some value, the value is interpreted + // as a list of class names to track + // + // Use the same |gTypesToLog| and |gSerialNumbers| to keep them + // consistent through the fork server and content processes. + // Without this, counters will be incorrect. + if (!gTypesToLog) { + gTypesToLog = new CharPtrSet(256); + } + + 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'; + } + if (!gTypesToLog->Contains(cp)) { + gTypesToLog->PutEntry(cp); + } + fprintf(stdout, "%s ", cp); + if (!cm) { + break; + } + *cm = ','; + cp = cm + 1; + } + fprintf(stdout, "\n"); + + if (!gSerialNumbers) { + gSerialNumbers = new SerialHash(256); + } + } else { + gTypesToLog = nullptr; + gSerialNumbers = nullptr; + } + + const char* objects = getenv("XPCOM_MEM_LOG_OBJECTS"); + if (objects) { + gObjectsToLog = new IntPtrSet(256); + + 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++) { + gObjectsToLog->PutEntry(serialno); + fprintf(stdout, "%" PRIdPTR " ", serialno); + } + if (!cm) { + break; + } + *cm = ','; + cp = cm + 1; + } + fprintf(stdout, "\n"); + } + } + + if (getenv("XPCOM_MEM_LOG_JS_STACK")) { + fprintf(stdout, "### XPCOM_MEM_LOG_JS_STACK defined\n"); + gLogJSStacks = true; + } + + if (gBloatLog) { + gLogging = OnlyBloatLogging; + } + + if (gRefcntsLog || gAllocLog || gCOMPtrLog) { + gLogging = FullLogging; + } +} + +static void InitTraceLog() { + if (gInitialized) { + return; + } + gInitialized = true; + + DoInitTraceLog(XRE_GetProcessTypeString()); +} + +extern "C" { + +static void EnsureWrite(FILE* aStream, const char* aBuf, size_t aLen) { +#ifdef XP_WIN + int fd = _fileno(aStream); +#else + int fd = fileno(aStream); +#endif + while (aLen > 0) { +#ifdef XP_WIN + auto written = _write(fd, aBuf, aLen); +#else + auto written = write(fd, aBuf, aLen); +#endif + if (written <= 0 || size_t(written) > aLen) { + break; + } + aBuf += written; + aLen -= written; + } +} + +static void PrintStackFrameCached(uint32_t aFrameNumber, void* aPC, void* aSP, + void* aClosure) { + auto stream = static_cast<FILE*>(aClosure); + static const int buflen = 1024; + char buf[buflen + 5] = " "; // 5 for leading " " and trailing '\n' + int len = + gCodeAddressService->GetLocation(aFrameNumber, aPC, buf + 4, buflen); + len = std::min(len, buflen + 1 - 2) + 4; + buf[len++] = '\n'; + buf[len] = '\0'; + fflush(stream); + EnsureWrite(stream, buf, len); +} + +static void RecordStackFrame(uint32_t /*aFrameNumber*/, void* aPC, + void* /*aSP*/, void* aClosure) { + auto locations = static_cast<std::vector<void*>*>(aClosure); + locations->push_back(aPC); +} +} + +/** + * This is a variant of |MozWalkTheStack| 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 + * |MozWalkTheStack|. 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, const void* aFirstFramePC) { + if (!gCodeAddressService) { + gCodeAddressService = new CodeAddressService<>(); + } + MozStackWalk(PrintStackFrameCached, aFirstFramePC, /* maxFrames */ 0, + aStream); +} + +static void WalkTheStackSavingLocations(std::vector<void*>& aLocations, + const void* aFirstFramePC) { + if (!gCodeAddressService) { + gCodeAddressService = new CodeAddressService<>(); + } + MozStackWalk(RecordStackFrame, aFirstFramePC, /* maxFrames */ 0, &aLocations); +} + +//---------------------------------------------------------------------- + +EXPORT_XPCOM_API(void) +NS_LogInit() { + NS_SetMainThread(); + + // FIXME: This is called multiple times, we should probably not allow that. + 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_GetProcessTypeString())) { + return; + } + + nsPrintfCString fileName("%sdmd-%" PRIPID ".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(MOZ_NEVER_INLINE void) +NS_LogAddRef(void* aPtr, nsrefcnt aRefcnt, const char* aClass, + uint32_t aClassSize) { + ASSERT_ACTIVITY_IS_LEGAL(aClass, "addrefed"); + if (!gInitialized) { + InitTraceLog(); + } + if (gLogging == NoLogging) { + return; + } + if (aRefcnt == 1 || gLogging == FullLogging) { + AutoTraceLogLock lock(gTraceLog); + + 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 || gTypesToLog->Contains(aClass)); + intptr_t serialno = 0; + if (gSerialNumbers && loggingThisType) { + serialno = GetSerialNumber(aPtr, aRefcnt == 1, CallerPC()); + MOZ_ASSERT(serialno != 0, + "Serial number requested for unrecognized pointer! " + "Are you memmoving a refcounted object?"); + auto record = gSerialNumbers->Get(aPtr); + if (record) { + ++record->refCount; + } + } + + 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, CallerPC()); + } + + 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, CallerPC()); + fflush(gRefcntsLog); + } + } +} + +EXPORT_XPCOM_API(MOZ_NEVER_INLINE void) +NS_LogRelease(void* aPtr, nsrefcnt aRefcnt, const char* aClass) { + ASSERT_ACTIVITY_IS_LEGAL(aClass, "released"); + if (!gInitialized) { + InitTraceLog(); + } + if (gLogging == NoLogging) { + return; + } + if (aRefcnt == 0 || gLogging == FullLogging) { + AutoTraceLogLock lock(gTraceLog); + + if (aRefcnt == 0 && gBloatLog) { + BloatEntry* entry = GetBloatEntry(aClass, 0); + if (entry) { + entry->Dtor(); + } + } + + bool loggingThisType = (!gTypesToLog || gTypesToLog->Contains(aClass)); + intptr_t serialno = 0; + if (gSerialNumbers && loggingThisType) { + serialno = GetSerialNumber(aPtr, false, CallerPC()); + MOZ_ASSERT(serialno != 0, + "Serial number requested for unrecognized pointer! " + "Are you memmoving a refcounted object?"); + auto record = gSerialNumbers->Get(aPtr); + if (record) { + --record->refCount; + } + } + + 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, CallerPC()); + 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, CallerPC()); + } + + if (aRefcnt == 0 && gSerialNumbers && loggingThisType) { + RecycleSerialNumberPtr(aPtr); + } + } +} + +EXPORT_XPCOM_API(MOZ_NEVER_INLINE void) +NS_LogCtor(void* aPtr, const char* aType, uint32_t aInstanceSize) { + ASSERT_ACTIVITY_IS_LEGAL(aType, "constructed"); + if (!gInitialized) { + InitTraceLog(); + } + + if (gLogging == NoLogging) { + return; + } + + AutoTraceLogLock lock(gTraceLog); + + if (gBloatLog) { + BloatEntry* entry = GetBloatEntry(aType, aInstanceSize); + if (entry) { + entry->Ctor(); + } + } + + bool loggingThisType = (!gTypesToLog || gTypesToLog->Contains(aType)); + intptr_t serialno = 0; + if (gSerialNumbers && loggingThisType) { + serialno = GetSerialNumber(aPtr, true, CallerPC()); + 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, CallerPC()); + } +} + +EXPORT_XPCOM_API(MOZ_NEVER_INLINE void) +NS_LogDtor(void* aPtr, const char* aType, uint32_t aInstanceSize) { + ASSERT_ACTIVITY_IS_LEGAL(aType, "destroyed"); + if (!gInitialized) { + InitTraceLog(); + } + + if (gLogging == NoLogging) { + return; + } + + AutoTraceLogLock lock(gTraceLog); + + if (gBloatLog) { + BloatEntry* entry = GetBloatEntry(aType, aInstanceSize); + if (entry) { + entry->Dtor(); + } + } + + bool loggingThisType = (!gTypesToLog || gTypesToLog->Contains(aType)); + intptr_t serialno = 0; + if (gSerialNumbers && loggingThisType) { + serialno = GetSerialNumber(aPtr, false, CallerPC()); + 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, CallerPC()); + } +} + +EXPORT_XPCOM_API(MOZ_NEVER_INLINE 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(gTraceLog); + + intptr_t serialno = GetSerialNumber(object, false, CallerPC()); + if (serialno == 0) { + return; + } + + auto record = gSerialNumbers->Get(object); + int32_t count = record ? ++record->COMPtrCount : -1; + bool loggingThisObject = (!gObjectsToLog || LogThisObj(serialno)); + + if (gCOMPtrLog && loggingThisObject) { + fprintf(gCOMPtrLog, "\n<?> %p %" PRIdPTR " nsCOMPtrAddRef %d %p\n", + object, serialno, count, aCOMPtr); + WalkTheStackCached(gCOMPtrLog, CallerPC()); + } + } +#endif // HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR +} + +EXPORT_XPCOM_API(MOZ_NEVER_INLINE 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(gTraceLog); + + intptr_t serialno = GetSerialNumber(object, false, CallerPC()); + if (serialno == 0) { + return; + } + + auto record = gSerialNumbers->Get(object); + int32_t count = record ? --record->COMPtrCount : -1; + bool loggingThisObject = (!gObjectsToLog || LogThisObj(serialno)); + + if (gCOMPtrLog && loggingThisObject) { + fprintf(gCOMPtrLog, "\n<?> %p %" PRIdPTR " nsCOMPtrRelease %d %p\n", + object, serialno, count, aCOMPtr); + WalkTheStackCached(gCOMPtrLog, CallerPC()); + } + } +#endif // HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR +} + +static void ClearLogs(bool aKeepCounters) { + gCodeAddressService = nullptr; + // These counters from the fork server process will be preserved + // for the content processes to keep them consistent. + if (!aKeepCounters) { + gBloatView = nullptr; + gTypesToLog = nullptr; + gSerialNumbers = nullptr; + } + gObjectsToLog = nullptr; + gLogJSStacks = false; + gLogLeaksOnly = false; + maybeUnregisterAndCloseFile(gBloatLog); + maybeUnregisterAndCloseFile(gRefcntsLog); + maybeUnregisterAndCloseFile(gAllocLog); + maybeUnregisterAndCloseFile(gCOMPtrLog); +} + +void nsTraceRefcnt::Shutdown() { ClearLogs(false); } + +void nsTraceRefcnt::SetActivityIsLegal(bool aLegal) { + if (gActivityTLS == BAD_TLS_INDEX) { + PR_NewThreadPrivateIndex(&gActivityTLS, nullptr); + } + + PR_SetThreadPrivate(gActivityTLS, reinterpret_cast<void*>(!aLegal)); +} + +void nsTraceRefcnt::StartLoggingClass(const char* aClass) { + gLogging = FullLogging; + + if (!gTypesToLog) { + gTypesToLog = new CharPtrSet(256); + } + + fprintf(stdout, "### StartLoggingClass %s\n", aClass); + if (!gTypesToLog->Contains(aClass)) { + gTypesToLog->PutEntry(aClass); + } + + // We are deliberately not initializing gSerialNumbers here, because + // it would cause assertions. gObjectsToLog can't be used because it + // relies on serial numbers. + +#ifdef XP_WIN +# define ENVVAR(x) u"" x +#else +# define ENVVAR(x) x +#endif + + if (!gRefcntsLog) { + InitLog(ENVVAR("XPCOM_MEM_LATE_REFCNT_LOG"), "refcounts", &gRefcntsLog, + XRE_GetProcessTypeString()); + } + +#undef ENVVAR +} + +#ifdef MOZ_ENABLE_FORKSERVER +void nsTraceRefcnt::ResetLogFiles(const char* aProcType) { + AutoRestore<LoggingType> saveLogging(gLogging); + gLogging = NoLogging; + + ClearLogs(true); + + // Create log files with the correct process type in the name. + DoInitTraceLog(aProcType); +} +#endif diff --git a/xpcom/base/nsTraceRefcnt.h b/xpcom/base/nsTraceRefcnt.h new file mode 100644 index 0000000000..23eeec0bdf --- /dev/null +++ b/xpcom/base/nsTraceRefcnt.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 nsTraceRefcnt_h +#define nsTraceRefcnt_h + +#include "nscore.h" + +class nsTraceRefcnt { + public: + static void Shutdown(); + + static nsresult DumpStatistics(); + + static void ResetStatistics(); + + /** + * 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); + + /** + * Start refcount logging aClass. If refcount logging has not already begun, + * it will use the environment variable XPCOM_MEM_LATE_REFCNT_LOG to decide + * where to make the log, in a similar way as the other nsTraceRefcnt logs. + */ + static void StartLoggingClass(const char* aClass); + +#ifdef MOZ_ENABLE_FORKSERVER + static void ResetLogFiles(const char* aProcType = nullptr); +#endif +}; + +#endif // nsTraceRefcnt_h diff --git a/xpcom/base/nsUUIDGenerator.cpp b/xpcom/base/nsUUIDGenerator.cpp new file mode 100644 index 0000000000..f00437ada7 --- /dev/null +++ b/xpcom/base/nsUUIDGenerator.cpp @@ -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/. */ + +#include "nsUUIDGenerator.h" + +NS_IMPL_ISUPPORTS(nsUUIDGenerator, nsIUUIDGenerator) + +nsUUIDGenerator::~nsUUIDGenerator() = default; + +NS_IMETHODIMP +nsUUIDGenerator::GenerateUUID(nsID** aRet) { + nsID* id = static_cast<nsID*>(moz_xmalloc(sizeof(nsID))); + + nsresult rv = GenerateUUIDInPlace(id); + if (NS_FAILED(rv)) { + free(id); + return rv; + } + + *aRet = id; + return rv; +} + +NS_IMETHODIMP +nsUUIDGenerator::GenerateUUIDInPlace(nsID* aId) { + return nsID::GenerateUUIDInPlace(*aId); +} diff --git a/xpcom/base/nsUUIDGenerator.h b/xpcom/base/nsUUIDGenerator.h new file mode 100644 index 0000000000..b1b2921a3b --- /dev/null +++ b/xpcom/base/nsUUIDGenerator.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 _NSUUIDGENERATOR_H_ +#define _NSUUIDGENERATOR_H_ + +#include "nsIUUIDGenerator.h" + +class nsUUIDGenerator final : public nsIUUIDGenerator { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + NS_DECL_NSIUUIDGENERATOR + + private: + ~nsUUIDGenerator(); +}; + +#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/nsVersionComparator.cpp b/xpcom/base/nsVersionComparator.cpp new file mode 100644 index 0000000000..f12e54272a --- /dev/null +++ b/xpcom/base/nsVersionComparator.cpp @@ -0,0 +1,401 @@ +/* -*- 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 "nsVersionComparator.h" + +#include <stdlib.h> +#include <string.h> +#include <stdint.h> +#include <errno.h> +#include "mozilla/CheckedInt.h" +#if defined(XP_WIN) && !defined(UPDATER_NO_STRING_GLUE_STL) +# include <wchar.h> +# include "nsString.h" +#endif + +struct VersionPart { + int32_t numA; + + const char* strB; // NOT null-terminated, can be a null pointer + uint32_t strBlen; + + int32_t numC; + + char* extraD; // null-terminated +}; + +#ifdef XP_WIN +struct VersionPartW { + int32_t numA; + + wchar_t* strB; // NOT null-terminated, can be a null pointer + uint32_t strBlen; + + int32_t numC; + + wchar_t* extraD; // null-terminated +}; +#endif + +static int32_t ns_strtol(const char* aPart, char** aNext) { + errno = 0; + long result_long = strtol(aPart, aNext, 10); + + // Different platforms seem to disagree on what to return when the value + // is out of range so we ensure that it is always what we want it to be. + // We choose 0 firstly because that is the default when the number doesn't + // exist at all and also because it would be easier to recover from should + // you somehow end up in a situation where an old version is invalid. It is + // much easier to create a version either larger or smaller than 0, much + // harder to do the same with INT_MAX. + if (errno != 0) { + return 0; + } + + mozilla::CheckedInt<int32_t> result = result_long; + if (!result.isValid()) { + return 0; + } + + return result.value(); +} + +/** + * Parse a version part into a number and "extra text". + * + * @returns A pointer to the next versionpart, or null if none. + */ +static char* ParseVP(char* aPart, VersionPart& aResult) { + char* dot; + + aResult.numA = 0; + aResult.strB = nullptr; + aResult.strBlen = 0; + aResult.numC = 0; + aResult.extraD = nullptr; + + if (!aPart) { + return aPart; + } + + dot = strchr(aPart, '.'); + if (dot) { + *dot = '\0'; + } + + if (aPart[0] == '*' && aPart[1] == '\0') { + aResult.numA = INT32_MAX; + aResult.strB = ""; + } else { + aResult.numA = ns_strtol(aPart, const_cast<char**>(&aResult.strB)); + } + + if (!*aResult.strB) { + aResult.strB = nullptr; + aResult.strBlen = 0; + } else { + if (aResult.strB[0] == '+') { + static const char kPre[] = "pre"; + + ++aResult.numA; + aResult.strB = kPre; + aResult.strBlen = sizeof(kPre) - 1; + } else { + const char* numstart = strpbrk(aResult.strB, "0123456789+-"); + if (!numstart) { + aResult.strBlen = strlen(aResult.strB); + } else { + aResult.strBlen = numstart - aResult.strB; + aResult.numC = ns_strtol(numstart, const_cast<char**>(&aResult.extraD)); + + if (!*aResult.extraD) { + aResult.extraD = nullptr; + } + } + } + } + + if (dot) { + ++dot; + + if (!*dot) { + dot = nullptr; + } + } + + return dot; +} + +/** + * Parse a version part into a number and "extra text". + * + * @returns A pointer to the next versionpart, or null if none. + */ +#ifdef XP_WIN + +static int32_t ns_wcstol(const wchar_t* aPart, wchar_t** aNext) { + errno = 0; + long result_long = wcstol(aPart, aNext, 10); + + // See above for the rationale for using 0 here. + if (errno != 0) { + return 0; + } + + mozilla::CheckedInt<int32_t> result = result_long; + if (!result.isValid()) { + return 0; + } + + return result.value(); +} + +static wchar_t* ParseVP(wchar_t* aPart, VersionPartW& aResult) { + wchar_t* dot; + + aResult.numA = 0; + aResult.strB = nullptr; + aResult.strBlen = 0; + aResult.numC = 0; + aResult.extraD = nullptr; + + if (!aPart) { + return aPart; + } + + dot = wcschr(aPart, '.'); + if (dot) { + *dot = '\0'; + } + + if (aPart[0] == '*' && aPart[1] == '\0') { + static wchar_t kEmpty[] = L""; + + aResult.numA = INT32_MAX; + aResult.strB = kEmpty; + } else { + aResult.numA = ns_wcstol(aPart, const_cast<wchar_t**>(&aResult.strB)); + } + + if (!*aResult.strB) { + aResult.strB = nullptr; + aResult.strBlen = 0; + } else { + if (aResult.strB[0] == '+') { + static wchar_t kPre[] = L"pre"; + + ++aResult.numA; + aResult.strB = kPre; + aResult.strBlen = sizeof(kPre) - 1; + } else { + const wchar_t* numstart = wcspbrk(aResult.strB, L"0123456789+-"); + if (!numstart) { + aResult.strBlen = wcslen(aResult.strB); + } else { + aResult.strBlen = numstart - aResult.strB; + aResult.numC = + ns_wcstol(numstart, const_cast<wchar_t**>(&aResult.extraD)); + + if (!*aResult.extraD) { + aResult.extraD = nullptr; + } + } + } + } + + if (dot) { + ++dot; + + if (!*dot) { + dot = nullptr; + } + } + + return dot; +} +#endif + +// compare two null-terminated strings, which may be null pointers +static int32_t ns_strcmp(const char* aStr1, const char* aStr2) { + // any string is *before* no string + if (!aStr1) { + return aStr2 != 0; + } + + if (!aStr2) { + return -1; + } + + return strcmp(aStr1, aStr2); +} + +// compare two length-specified string, which may be null pointers +static int32_t ns_strnncmp(const char* aStr1, uint32_t aLen1, const char* aStr2, + uint32_t aLen2) { + // any string is *before* no string + if (!aStr1) { + return aStr2 != 0; + } + + if (!aStr2) { + return -1; + } + + for (; aLen1 && aLen2; --aLen1, --aLen2, ++aStr1, ++aStr2) { + if (*aStr1 < *aStr2) { + return -1; + } + + if (*aStr1 > *aStr2) { + return 1; + } + } + + if (aLen1 == 0) { + return aLen2 == 0 ? 0 : -1; + } + + return 1; +} + +// compare two int32_t +static int32_t ns_cmp(int32_t aNum1, int32_t aNum2) { + if (aNum1 < aNum2) { + return -1; + } + + return aNum1 != aNum2; +} + +/** + * Compares two VersionParts + */ +static int32_t CompareVP(VersionPart& aVer1, VersionPart& aVer2) { + int32_t r = ns_cmp(aVer1.numA, aVer2.numA); + if (r) { + return r; + } + + r = ns_strnncmp(aVer1.strB, aVer1.strBlen, aVer2.strB, aVer2.strBlen); + if (r) { + return r; + } + + r = ns_cmp(aVer1.numC, aVer2.numC); + if (r) { + return r; + } + + return ns_strcmp(aVer1.extraD, aVer2.extraD); +} + +/** + * Compares two VersionParts + */ +#ifdef XP_WIN +static int32_t CompareVP(VersionPartW& aVer1, VersionPartW& aVer2) { + int32_t r = ns_cmp(aVer1.numA, aVer2.numA); + if (r) { + return r; + } + + r = wcsncmp(aVer1.strB, aVer2.strB, XPCOM_MIN(aVer1.strBlen, aVer2.strBlen)); + if (r) { + return r; + } + + r = ns_cmp(aVer1.numC, aVer2.numC); + if (r) { + return r; + } + + if (!aVer1.extraD) { + return aVer2.extraD != 0; + } + + if (!aVer2.extraD) { + return -1; + } + + return wcscmp(aVer1.extraD, aVer2.extraD); +} +#endif + +namespace mozilla { + +#ifdef XP_WIN +int32_t CompareVersions(const char16_t* aStrA, const char16_t* aStrB) { + wchar_t* A2 = wcsdup(char16ptr_t(aStrA)); + if (!A2) { + return 1; + } + + wchar_t* B2 = wcsdup(char16ptr_t(aStrB)); + if (!B2) { + free(A2); + return 1; + } + + int32_t result; + wchar_t* a = A2; + wchar_t* b = B2; + + do { + VersionPartW va, vb; + + a = ParseVP(a, va); + b = ParseVP(b, vb); + + result = CompareVP(va, vb); + if (result) { + break; + } + + } while (a || b); + + free(A2); + free(B2); + + return result; +} +#endif + +int32_t CompareVersions(const char* aStrA, const char* aStrB) { + char* A2 = strdup(aStrA); + if (!A2) { + return 1; + } + + char* B2 = strdup(aStrB); + if (!B2) { + free(A2); + return 1; + } + + int32_t result; + char* a = A2; + char* b = B2; + + do { + VersionPart va, vb; + + a = ParseVP(a, va); + b = ParseVP(b, vb); + + result = CompareVP(va, vb); + if (result) { + break; + } + + } while (a || b); + + free(A2); + free(B2); + + return result; +} + +} // namespace mozilla diff --git a/xpcom/base/nsVersionComparator.h b/xpcom/base/nsVersionComparator.h new file mode 100644 index 0000000000..32ea620583 --- /dev/null +++ b/xpcom/base/nsVersionComparator.h @@ -0,0 +1,112 @@ +/* -*- 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/. */ + +// NB: This code may be used from non-XPCOM code, in particular, the +// standalone updater executable. + +#ifndef nsVersionComparator_h__ +#define nsVersionComparator_h__ + +#include "mozilla/Char16.h" +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#if defined(XP_WIN) && !defined(UPDATER_NO_STRING_GLUE_STL) +# include <wchar.h> +# include "nsString.h" +#endif + +/** + * In order to compare version numbers in Mozilla, you need to use the + * mozilla::Version class. You can construct an object of this type by passing + * in a string version number to the constructor. Objects of this type can be + * compared using the standard comparison operators. + * + * For example, let's say that you want to make sure that a given version + * number is not older than 15.a2. Here's how you would write a function to + * do that. + * + * bool IsVersionValid(const char* version) { + * return mozilla::Version("15.a2") <= mozilla::Version(version); + * } + * + * Or, since Version's constructor is implicit, you can simplify this code: + * + * bool IsVersionValid(const char* version) { + * return mozilla::Version("15.a2") <= version; + * } + */ + +namespace mozilla { + +/** + * Compares the version strings provided. + * + * Returns 0 if the versions match, < 0 if aStrB > aStrA and > 0 if + * aStrA > aStrB. + */ +int32_t CompareVersions(const char* aStrA, const char* aStrB); + +#ifdef XP_WIN +/** + * As above but for wide character strings. + */ +int32_t CompareVersions(const char16_t* aStrA, const char16_t* aStrB); +#endif + +struct Version { + explicit Version(const char* aVersionString) { + versionContent = strdup(aVersionString); + } + + const char* ReadContent() const { return versionContent; } + + ~Version() { free(versionContent); } + + bool operator<(const Version& aRhs) const { + return CompareVersions(versionContent, aRhs.ReadContent()) < 0; + } + bool operator<=(const Version& aRhs) const { + return CompareVersions(versionContent, aRhs.ReadContent()) < 1; + } + bool operator>(const Version& aRhs) const { + return CompareVersions(versionContent, aRhs.ReadContent()) > 0; + } + bool operator>=(const Version& aRhs) const { + return CompareVersions(versionContent, aRhs.ReadContent()) > -1; + } + bool operator==(const Version& aRhs) const { + return CompareVersions(versionContent, aRhs.ReadContent()) == 0; + } + bool operator!=(const Version& aRhs) const { + return CompareVersions(versionContent, aRhs.ReadContent()) != 0; + } + bool operator<(const char* aRhs) const { + return CompareVersions(versionContent, aRhs) < 0; + } + bool operator<=(const char* aRhs) const { + return CompareVersions(versionContent, aRhs) < 1; + } + bool operator>(const char* aRhs) const { + return CompareVersions(versionContent, aRhs) > 0; + } + bool operator>=(const char* aRhs) const { + return CompareVersions(versionContent, aRhs) > -1; + } + bool operator==(const char* aRhs) const { + return CompareVersions(versionContent, aRhs) == 0; + } + bool operator!=(const char* aRhs) const { + return CompareVersions(versionContent, aRhs) != 0; + } + + private: + char* versionContent; +}; + +} // namespace mozilla + +#endif // nsVersionComparator_h__ diff --git a/xpcom/base/nsVersionComparatorImpl.cpp b/xpcom/base/nsVersionComparatorImpl.cpp new file mode 100644 index 0000000000..b52395bbac --- /dev/null +++ b/xpcom/base/nsVersionComparatorImpl.cpp @@ -0,0 +1,20 @@ +/* -*- 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 0000000000..49b6f157a1 --- /dev/null +++ b/xpcom/base/nsVersionComparatorImpl.h @@ -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/Attributes.h" + +#include "nsIVersionComparator.h" + +class nsVersionComparatorImpl final : public nsIVersionComparator { + ~nsVersionComparatorImpl() = default; + + 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/nsWeakReference.cpp b/xpcom/base/nsWeakReference.cpp new file mode 100644 index 0000000000..958ef08451 --- /dev/null +++ b/xpcom/base/nsWeakReference.cpp @@ -0,0 +1,151 @@ +/* -*- 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/. */ + +// nsWeakReference.cpp + +#include "mozilla/Attributes.h" + +#include "nsWeakReference.h" +#include "nsCOMPtr.h" +#include "nsDebug.h" + +class nsWeakReference final : public nsIWeakReference { + public: + // nsISupports... + NS_DECL_ISUPPORTS + + // nsIWeakReference... + NS_DECL_NSIWEAKREFERENCE + + private: + friend class nsSupportsWeakReference; + + explicit nsWeakReference(nsSupportsWeakReference* aReferent) + : nsIWeakReference(aReferent) + // ...I can only be constructed by an |nsSupportsWeakReference| + {} + + ~nsWeakReference() + // ...I will only be destroyed by calling |delete| myself. + { + MOZ_WEAKREF_ASSERT_OWNINGTHREAD; + if (mObject) { + static_cast<nsSupportsWeakReference*>(mObject)->NoticeProxyDestruction(); + } + } + + void NoticeReferentDestruction() + // ...called (only) by an |nsSupportsWeakReference| from _its_ dtor. + { + MOZ_WEAKREF_ASSERT_OWNINGTHREAD; + mObject = nullptr; + } +}; + +nsresult nsQueryReferent::operator()(const nsIID& aIID, void** aAnswer) const { + nsresult status; + if (mWeakPtr) { + if (NS_FAILED(status = mWeakPtr->QueryReferent(aIID, aAnswer))) { + *aAnswer = 0; + } + } else { + status = NS_ERROR_NULL_POINTER; + } + + if (mErrorPtr) { + *mErrorPtr = status; + } + return status; +} + +nsIWeakReference* NS_GetWeakReference(nsISupportsWeakReference* aInstancePtr, + nsresult* aErrorPtr) { + nsresult status; + + nsIWeakReference* result = nullptr; + + if (aInstancePtr) { + status = aInstancePtr->GetWeakReference(&result); + } else { + status = NS_ERROR_NULL_POINTER; + } + + if (aErrorPtr) { + *aErrorPtr = status; + } + + return result; +} + +nsIWeakReference* // or else |already_AddRefed<nsIWeakReference>| +NS_GetWeakReference(nsISupports* aInstancePtr, nsresult* aErrorPtr) { + nsresult status; + + nsIWeakReference* result = nullptr; + + if (aInstancePtr) { + nsCOMPtr<nsISupportsWeakReference> factoryPtr = + do_QueryInterface(aInstancePtr, &status); + if (factoryPtr) { + status = factoryPtr->GetWeakReference(&result); + } + // else, |status| has already been set by |do_QueryInterface| + } else { + status = NS_ERROR_NULL_POINTER; + } + + if (aErrorPtr) { + *aErrorPtr = status; + } + return result; +} + +nsresult nsSupportsWeakReference::GetWeakReference( + nsIWeakReference** aInstancePtr) { + if (!aInstancePtr) { + return NS_ERROR_NULL_POINTER; + } + + if (!mProxy) { + mProxy = new nsWeakReference(this); + } else { + MOZ_WEAKREF_ASSERT_OWNINGTHREAD_DELEGATED(mProxy); + } + RefPtr<nsWeakReference> rval = mProxy; + rval.forget(aInstancePtr); + + return NS_OK; +} + +NS_IMPL_ISUPPORTS(nsWeakReference, nsIWeakReference) + +NS_IMETHODIMP +nsWeakReference::QueryReferentFromScript(const nsIID& aIID, + void** aInstancePtr) { + return QueryReferent(aIID, aInstancePtr); +} + +nsresult nsIWeakReference::QueryReferent(const nsIID& aIID, + void** aInstancePtr) { + MOZ_WEAKREF_ASSERT_OWNINGTHREAD; + + if (!mObject) { + return NS_ERROR_NULL_POINTER; + } + + return mObject->QueryInterface(aIID, aInstancePtr); +} + +size_t nsWeakReference::SizeOfOnlyThis(mozilla::MallocSizeOf aMallocSizeOf) { + return aMallocSizeOf(this); +} + +void nsSupportsWeakReference::ClearWeakReferences() { + if (mProxy) { + mProxy->NoticeReferentDestruction(); + mProxy = nullptr; + } +} diff --git a/xpcom/base/nsWeakReference.h b/xpcom/base/nsWeakReference.h new file mode 100644 index 0000000000..5303f2d2ea --- /dev/null +++ b/xpcom/base/nsWeakReference.h @@ -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/. */ + +#ifndef nsWeakReference_h__ +#define nsWeakReference_h__ + +// nsWeakReference.h + +// See mfbt/WeakPtr.h for a more typesafe C++ implementation of weak references + +#include "nsIWeakReferenceUtils.h" + +class nsWeakReference; + +class nsSupportsWeakReference : public nsISupportsWeakReference { + public: + nsSupportsWeakReference() : mProxy(0) {} + + NS_DECL_NSISUPPORTSWEAKREFERENCE + + protected: + inline ~nsSupportsWeakReference(); + + private: + friend class nsWeakReference; + + // Called (only) by an |nsWeakReference| from _its_ dtor. + // The thread safety check is made by the caller. + void NoticeProxyDestruction() { mProxy = nullptr; } + + nsWeakReference* MOZ_NON_OWNING_REF mProxy; + + protected: + void ClearWeakReferences(); + bool HasWeakReferences() const { return !!mProxy; } +}; + +inline nsSupportsWeakReference::~nsSupportsWeakReference() { + ClearWeakReferences(); +} + +#define NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE \ + tmp->ClearWeakReferences(); + +#define NS_IMPL_CYCLE_COLLECTION_WEAK(class_, ...) \ + NS_IMPL_CYCLE_COLLECTION_CLASS(class_) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(class_) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK(__VA_ARGS__) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE \ + NS_IMPL_CYCLE_COLLECTION_UNLINK_END \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(class_) \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(__VA_ARGS__) \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +#define NS_IMPL_CYCLE_COLLECTION_WEAK_INHERITED(class_, super_, ...) \ + NS_IMPL_CYCLE_COLLECTION_CLASS(class_) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(class_, super_) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK(__VA_ARGS__) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE \ + NS_IMPL_CYCLE_COLLECTION_UNLINK_END \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(class_, super_) \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(__VA_ARGS__) \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +#endif diff --git a/xpcom/base/nsWindowsHelpers.h b/xpcom/base/nsWindowsHelpers.h new file mode 100644 index 0000000000..7c91275803 --- /dev/null +++ b/xpcom/base/nsWindowsHelpers.h @@ -0,0 +1,339 @@ +/* -*- 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/. */ + +// NB: This code may be used from non-XPCOM code, in particular, the +// Windows Default Browser Agent. + +#ifndef nsWindowsHelpers_h +#define nsWindowsHelpers_h + +#include <windows.h> +#include <msi.h> +#include "nsAutoRef.h" +#include "mozilla/Assertions.h" +#include "mozilla/UniquePtr.h" + +// ---------------------------------------------------------------------------- +// Critical Section helper class +// ---------------------------------------------------------------------------- + +class AutoCriticalSection { + public: + explicit 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<HFONT> { + public: + typedef HFONT RawRef; + static HFONT Void() { return nullptr; } + + static void Release(RawRef aFD) { + if (aFD != Void()) { + ::DeleteObject(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) {} + + explicit 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); + } + } +}; + +template <> +class nsAutoRefTraits<MSIHANDLE> { + public: + typedef MSIHANDLE RawRef; + static RawRef Void() { return 0; } + + static void Release(RawRef aHandle) { + if (aHandle != Void()) { + ::MsiCloseHandle(aHandle); + } + } +}; + +// 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: + MOZ_IMPLICIT 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: + MOZ_IMPLICIT 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<HFONT> nsAutoFont; +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; +typedef nsAutoRef<MSIHANDLE> nsAutoMsiHandle; + +// 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) { + return LoadLibraryExW(aModule, nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); +} + +// for UniquePtr +struct LocalFreeDeleter { + void operator()(void* aPtr) { ::LocalFree(aPtr); } +}; + +struct VirtualFreeDeleter { + void operator()(void* aPtr) { ::VirtualFree(aPtr, 0, MEM_RELEASE); } +}; + +// for UniquePtr to store a PSID +struct FreeSidDeleter { + void operator()(void* aPtr) { ::FreeSid(aPtr); } +}; +// Unfortunately, although SID is a struct, PSID is a void* +// This typedef will work for storing a PSID in a UniquePtr and should make +// things a bit more readable. +typedef mozilla::UniquePtr<void, FreeSidDeleter> UniqueSidPtr; + +struct CloseHandleDeleter { + typedef HANDLE pointer; + void operator()(pointer aHandle) { + if (aHandle != INVALID_HANDLE_VALUE) { + ::CloseHandle(aHandle); + } + } +}; + +// One caller of this function is early in startup and several others are not, +// so they have different ways of determining the two parameters. This function +// exists just so any future code that needs to determine whether the dynamic +// blocklist is disabled remembers to check whether safe mode is active. +inline bool IsDynamicBlocklistDisabled(bool isSafeMode, + bool hasCommandLineDisableArgument) { + return isSafeMode || hasCommandLineDisableArgument; +} +#endif diff --git a/xpcom/base/nscore.h b/xpcom/base/nscore.h new file mode 100644 index 0000000000..8881ddf5c8 --- /dev/null +++ b/xpcom/base/nscore.h @@ -0,0 +1,257 @@ +/* -*- 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___ + +/* Definitions of functions and operators that allocate memory. */ +#if !defined(NS_NO_XPCOM) && !defined(MOZ_NO_MOZALLOC) +# include "mozilla/mozalloc.h" +#endif + +/** + * Incorporate the integer data types which XPCOM uses. + */ +#include <stddef.h> // IWYU pragma: export +#include <stdint.h> // IWYU pragma: export + +#include "mozilla/HelperMacros.h" // IWYU pragma: export +#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 + +/** + * Various API modifiers. + * + * - NS_IMETHOD/NS_IMETHOD_: use for in-class declarations and definitions. + * - NS_IMETHODIMP/NS_IMETHODIMP_: use for out-of-class definitions. + * - NS_METHOD_: usually used in conjunction with NS_CALLBACK_. Best avoided. + * - NS_CALLBACK_: used in some legacy situations. Best avoided. + */ + +#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 + +#define NS_IMETHOD NS_IMETHOD_(nsresult) +#define NS_IMETHODIMP NS_IMETHODIMP_(nsresult) + +/** + * Import/Export macros for XPCOM APIs + */ + +#define EXPORT_XPCOM_API(type) type +#define IMPORT_XPCOM_API(type) type +#define GLUE_XPCOM_API(type) type + +#ifdef __cplusplus +# define NS_EXTERN_C extern "C" +#else +# define NS_EXTERN_C +#endif + +#define XPCOM_API(type) NS_EXTERN_C type + +/* 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. + * + * Note that we're also setting this for code coverage and pgo profile + * generation, because both of those require atexit hooks, which won't fire + * if we're using _exit. Bug 1555974 covers improving this. + * + */ +#ifndef NS_FREE_PERMANENT_DATA +# if defined(NS_BUILD_REFCNT_LOGGING) || defined(MOZ_VALGRIND) || \ + defined(MOZ_ASAN) || defined(MOZ_TSAN) || defined(MOZ_CODE_COVERAGE) || \ + defined(MOZ_PROFILE_GENERATE) || defined(JS_STRUCTURED_SPEW) +# define NS_FREE_PERMANENT_DATA +# endif +#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" // IWYU pragma: export + +typedef MozRefCountType nsrefcnt; + +namespace mozilla { +// Extensions to the mozilla::Result type for handling of nsresult values. +// +// Note that these specializations need to be defined before Result.h is +// included, or we run into explicit specialization after instantiation errors, +// especially if Result.h is used in multiple sources in a unified compile. + +namespace detail { +// When used as an error value, nsresult should never be NS_OK. +// This specialization allows us to pack Result<Ok, nsresult> into a +// nsresult-sized value. +template <typename T> +struct UnusedZero; +template <> +struct UnusedZero<nsresult> { + using StorageType = nsresult; + + static constexpr bool value = true; + static constexpr StorageType nullValue = NS_OK; + + static constexpr void AssertValid(StorageType aValue) {} + static constexpr const nsresult& Inspect(const StorageType& aValue) { + return aValue; + } + static constexpr nsresult Unwrap(StorageType aValue) { return aValue; } + static constexpr StorageType Store(nsresult aValue) { return aValue; } +}; +} // namespace detail + +template <typename T> +class GenericErrorResult; +template <> +class GenericErrorResult<nsresult>; + +struct Ok; +template <typename V, typename E> +class Result; + +// Allow MOZ_TRY to handle `nsresult` values. +template <typename E = nsresult> +inline Result<Ok, E> ToResult(nsresult aValue); +} // namespace mozilla + +/* + * 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)) + +/* + * 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 0000000000..28e643e312 --- /dev/null +++ b/xpcom/base/nsrootidl.idl @@ -0,0 +1,105 @@ +/* -*- 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" +#include "nsID.h" +typedef int64_t PRTime; + +/* + * Forward declarations for new string types + */ +#include "nsStringFwd.h" + +struct JSContext; + +/* + * Forward declaration of mozilla::dom::Promise + */ +namespace mozilla { +namespace dom { +class Promise; +} // namespace dom +} // namespace mozilla + +/* + * 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 nsresult ; + +// If we ever want to use `size_t` in scriptable interfaces, this will need to +// be built into the xpidl compiler, as the size varies based on platform. + native size_t(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, 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); + +[ptr, promise] native Promise(ignored); + +%{C++ +/* + * End commenting out the C++ versions of the above in the output header + */ +#endif +%} |